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__)
33 float con_cursorspeed = 4;
35 // lines up from bottom to display
39 void *con_mutex = NULL;
41 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
42 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
43 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
45 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
46 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
47 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
49 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
50 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
51 cvar_t con_chatpos = {CVAR_SAVE, "con_chatpos","0", "where to put chat (negative: lines from bottom of screen, positive: lines below notify, 0: at top)"};
52 cvar_t con_chatrect = {CVAR_SAVE, "con_chatrect","0", "use con_chatrect_x and _y to position con_notify and con_chat freely instead of con_chatpos"};
53 cvar_t con_chatrect_x = {CVAR_SAVE, "con_chatrect_x","", "where to put chat, relative x coordinate of left edge on screen (use con_chatwidth for width)"};
54 cvar_t con_chatrect_y = {CVAR_SAVE, "con_chatrect_y","", "where to put chat, relative y coordinate of top edge on screen (use con_chat for line count)"};
55 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
56 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
57 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
58 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
59 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
62 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)"};
64 cvar_t sys_colortranslation = {0, "sys_colortranslation", "0", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
66 cvar_t sys_colortranslation = {0, "sys_colortranslation", "1", "terminal console color translation (supported values: 0 = strip color codes, 1 = translate to ANSI codes, 2 = no translation)"};
70 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
71 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
72 "0: add nothing after completion. "
73 "1: add the last color after completion. "
74 "2: add a quote when starting a quote instead of the color. "
75 "4: will replace 1, will force color, even after a quote. "
76 "8: ignore non-alphanumerics. "
77 "16: ignore spaces. "};
78 #define NICKS_ADD_COLOR 1
79 #define NICKS_ADD_QUOTE 2
80 #define NICKS_FORCE_COLOR 4
81 #define NICKS_ALPHANUMERICS_ONLY 8
82 #define NICKS_NO_SPACES 16
84 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
85 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
86 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
91 qboolean con_initialized;
93 // used for server replies to rcon command
94 lhnetsocket_t *rcon_redirect_sock = NULL;
95 lhnetaddress_t *rcon_redirect_dest = NULL;
96 int rcon_redirect_bufferpos = 0;
97 char rcon_redirect_buffer[1400];
98 qboolean rcon_redirect_proquakeprotocol = false;
100 // generic functions for console buffers
102 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
105 buf->textsize = textsize;
106 buf->text = (char *) Mem_Alloc(mempool, textsize);
107 buf->maxlines = maxlines;
108 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
109 buf->lines_first = 0;
110 buf->lines_count = 0;
118 void ConBuffer_Clear (conbuffer_t *buf)
120 buf->lines_count = 0;
128 void ConBuffer_Shutdown(conbuffer_t *buf)
134 Mem_Free(buf->lines);
143 Notifies the console code about the current time
144 (and shifts back times of other entries when the time
148 void ConBuffer_FixTimes(conbuffer_t *buf)
151 if(buf->lines_count >= 1)
153 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
156 for(i = 0; i < buf->lines_count; ++i)
157 CONBUFFER_LINES(buf, i).addtime += diff;
166 Deletes the first line from the console history.
169 void ConBuffer_DeleteLine(conbuffer_t *buf)
171 if(buf->lines_count == 0)
174 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
179 ConBuffer_DeleteLastLine
181 Deletes the last line from the console history.
184 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
186 if(buf->lines_count == 0)
195 Checks if there is space for a line of the given length, and if yes, returns a
196 pointer to the start of such a space, and NULL otherwise.
199 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
201 if(len > buf->textsize)
203 if(buf->lines_count == 0)
207 char *firstline_start = buf->lines[buf->lines_first].start;
208 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
209 // the buffer is cyclic, so we first have two cases...
210 if(firstline_start < lastline_onepastend) // buffer is contiguous
213 if(len <= buf->text + buf->textsize - lastline_onepastend)
214 return lastline_onepastend;
216 else if(len <= firstline_start - buf->text)
221 else // buffer has a contiguous hole
223 if(len <= firstline_start - lastline_onepastend)
224 return lastline_onepastend;
235 Appends a given string as a new line to the console.
238 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
243 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
247 ConBuffer_FixTimes(buf);
249 if(len >= buf->textsize)
252 // only display end of line.
253 line += len - buf->textsize + 1;
254 len = buf->textsize - 1;
256 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
257 ConBuffer_DeleteLine(buf);
258 memcpy(putpos, line, len);
262 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
264 p = &CONBUFFER_LINES_LAST(buf);
267 p->addtime = cl.time;
269 p->height = -1; // calculate when needed
272 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
276 start = buf->lines_count;
277 for(i = start - 1; i >= 0; --i)
279 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
281 if((l->mask & mask_must) != mask_must)
283 if(l->mask & mask_mustnot)
292 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
294 static char copybuf[MAX_INPUTLINE]; // client only
295 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
296 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
297 strlcpy(copybuf, l->start, sz);
302 ==============================================================================
306 ==============================================================================
311 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
312 cvar_t log_dest_udp = {0, "log_dest_udp","", "UDP address to log messages to (in QW rcon compatible format); multiple destinations can be separated by spaces; DO NOT SPECIFY DNS NAMES HERE"};
313 char log_dest_buffer[1400]; // UDP packet
314 size_t log_dest_buffer_pos;
315 unsigned int log_dest_buffer_appending;
316 char crt_log_file [MAX_OSPATH] = "";
317 qfile_t* logfile = NULL;
319 unsigned char* logqueue = NULL;
321 size_t logq_size = 0;
323 void Log_ConPrint (const char *msg);
325 static void Log_DestBuffer_Init(void)
327 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
328 log_dest_buffer_pos = 5;
331 static void Log_DestBuffer_Flush_NoLock(void)
333 lhnetaddress_t log_dest_addr;
334 lhnetsocket_t *log_dest_socket;
335 const char *s = log_dest_udp.string;
336 qboolean have_opened_temp_sockets = false;
337 if(s) if(log_dest_buffer_pos > 5)
339 ++log_dest_buffer_appending;
340 log_dest_buffer[log_dest_buffer_pos++] = 0;
342 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
344 have_opened_temp_sockets = true;
345 NetConn_OpenServerPorts(true);
348 while(COM_ParseToken_Console(&s))
349 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
351 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
353 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
355 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
358 if(have_opened_temp_sockets)
359 NetConn_CloseServerPorts();
360 --log_dest_buffer_appending;
362 log_dest_buffer_pos = 0;
370 void Log_DestBuffer_Flush(void)
373 Thread_LockMutex(con_mutex);
374 Log_DestBuffer_Flush_NoLock();
376 Thread_UnlockMutex(con_mutex);
379 static const char* Log_Timestamp (const char *desc)
381 static char timestamp [128]; // init/shutdown only
388 char timestring [64];
390 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
393 localtime_s (&crt_tm, &crt_time);
394 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
396 crt_tm = localtime (&crt_time);
397 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
401 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
403 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
408 static void Log_Open (void)
410 if (logfile != NULL || log_file.string[0] == '\0')
413 logfile = FS_OpenRealFile(log_file.string, "a", false);
416 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
417 FS_Print (logfile, Log_Timestamp ("Log started"));
426 void Log_Close (void)
431 FS_Print (logfile, Log_Timestamp ("Log stopped"));
432 FS_Print (logfile, "\n");
436 crt_log_file[0] = '\0';
445 void Log_Start (void)
451 // Dump the contents of the log queue into the log file and free it
452 if (logqueue != NULL)
454 unsigned char *temp = logqueue;
459 FS_Write (logfile, temp, logq_ind);
460 if(*log_dest_udp.string)
462 for(pos = 0; pos < logq_ind; )
464 if(log_dest_buffer_pos == 0)
465 Log_DestBuffer_Init();
466 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
467 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
468 log_dest_buffer_pos += n;
469 Log_DestBuffer_Flush_NoLock();
486 void Log_ConPrint (const char *msg)
488 static qboolean inprogress = false;
490 // don't allow feedback loops with memory error reports
495 // Until the host is completely initialized, we maintain a log queue
496 // to store the messages, since the log can't be started before
497 if (logqueue != NULL)
499 size_t remain = logq_size - logq_ind;
500 size_t len = strlen (msg);
502 // If we need to enlarge the log queue
505 size_t factor = ((logq_ind + len) / logq_size) + 1;
506 unsigned char* newqueue;
509 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
510 memcpy (newqueue, logqueue, logq_ind);
513 remain = logq_size - logq_ind;
515 memcpy (&logqueue[logq_ind], msg, len);
522 // Check if log_file has changed
523 if (strcmp (crt_log_file, log_file.string) != 0)
529 // If a log file is available
531 FS_Print (logfile, msg);
542 void Log_Printf (const char *logfilename, const char *fmt, ...)
546 file = FS_OpenRealFile(logfilename, "a", true);
551 va_start (argptr, fmt);
552 FS_VPrintf (file, fmt, argptr);
561 ==============================================================================
565 ==============================================================================
573 void Con_ToggleConsole_f (void)
575 if (COM_CheckParm ("-noconsole"))
576 if (!(key_consoleactive & KEY_CONSOLEACTIVE_USER))
577 return; // only allow the key bind to turn off console
579 // toggle the 'user wants console' bit
580 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
589 void Con_ClearNotify (void)
592 for(i = 0; i < CON_LINES_COUNT; ++i)
593 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
602 static void Con_MessageMode_f (void)
604 key_dest = key_message;
605 chat_mode = 0; // "say"
608 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
609 chat_bufferlen = strlen(chat_buffer);
619 static void Con_MessageMode2_f (void)
621 key_dest = key_message;
622 chat_mode = 1; // "say_team"
625 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
626 chat_bufferlen = strlen(chat_buffer);
635 static void Con_CommandMode_f (void)
637 key_dest = key_message;
640 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
641 chat_bufferlen = strlen(chat_buffer);
643 chat_mode = -1; // command
651 void Con_CheckResize (void)
656 f = bound(1, con_textsize.value, 128);
657 if(f != con_textsize.value)
658 Cvar_SetValueQuick(&con_textsize, f);
659 width = (int)floor(vid_conwidth.value / con_textsize.value);
660 width = bound(1, width, con.textsize/4);
661 // FIXME uses con in a non abstracted way
663 if (width == con_linewidth)
666 con_linewidth = width;
668 for(i = 0; i < CON_LINES_COUNT; ++i)
669 CON_LINES(i).height = -1; // recalculate when next needed
675 //[515]: the simplest command ever
676 //LordHavoc: not so simple after I made it print usage...
677 static void Con_Maps_f (void)
681 Con_Printf("usage: maps [mapnameprefix]\n");
684 else if (Cmd_Argc() == 2)
685 GetMapList(Cmd_Argv(1), NULL, 0);
687 GetMapList("", NULL, 0);
690 static void Con_ConDump_f (void)
696 Con_Printf("usage: condump <filename>\n");
699 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
702 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
705 if (con_mutex) Thread_LockMutex(con_mutex);
706 for(i = 0; i < CON_LINES_COUNT; ++i)
708 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
709 FS_Write(file, "\n", 1);
711 if (con_mutex) Thread_UnlockMutex(con_mutex);
715 void Con_Clear_f (void)
717 if (con_mutex) Thread_LockMutex(con_mutex);
718 ConBuffer_Clear(&con);
719 if (con_mutex) Thread_UnlockMutex(con_mutex);
730 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
731 if (Thread_HasThreads())
732 con_mutex = Thread_CreateMutex();
734 // Allocate a log queue, this will be freed after configs are parsed
735 logq_size = MAX_INPUTLINE;
736 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
739 Cvar_RegisterVariable (&sys_colortranslation);
740 Cvar_RegisterVariable (&sys_specialcharactertranslation);
742 Cvar_RegisterVariable (&log_file);
743 Cvar_RegisterVariable (&log_dest_udp);
745 // support for the classic Quake option
746 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
747 if (COM_CheckParm ("-condebug") != 0)
748 Cvar_SetQuick (&log_file, "qconsole.log");
750 // register our cvars
751 Cvar_RegisterVariable (&con_chat);
752 Cvar_RegisterVariable (&con_chatpos);
753 Cvar_RegisterVariable (&con_chatrect_x);
754 Cvar_RegisterVariable (&con_chatrect_y);
755 Cvar_RegisterVariable (&con_chatrect);
756 Cvar_RegisterVariable (&con_chatsize);
757 Cvar_RegisterVariable (&con_chattime);
758 Cvar_RegisterVariable (&con_chatwidth);
759 Cvar_RegisterVariable (&con_notify);
760 Cvar_RegisterVariable (&con_notifyalign);
761 Cvar_RegisterVariable (&con_notifysize);
762 Cvar_RegisterVariable (&con_notifytime);
763 Cvar_RegisterVariable (&con_textsize);
764 Cvar_RegisterVariable (&con_chatsound);
767 Cvar_RegisterVariable (&con_nickcompletion);
768 Cvar_RegisterVariable (&con_nickcompletion_flags);
770 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
771 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
772 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
774 // register our commands
775 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
776 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
777 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
778 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
779 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
780 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
781 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
783 con_initialized = true;
784 Con_DPrint("Console initialized.\n");
787 void Con_Shutdown (void)
789 if (con_mutex) Thread_LockMutex(con_mutex);
790 ConBuffer_Shutdown(&con);
791 if (con_mutex) Thread_UnlockMutex(con_mutex);
792 if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
799 Handles cursor positioning, line wrapping, etc
800 All console printing must go through this in order to be displayed
801 If no console is visible, the notify window will pop up.
804 static void Con_PrintToHistory(const char *txt, int mask)
807 // \n goes to next line
808 // \r deletes current line and makes a new one
810 static int cr_pending = 0;
811 static char buf[CON_TEXTSIZE]; // con_mutex
812 static int bufpos = 0;
814 if(!con.text) // FIXME uses a non-abstracted property of con
821 ConBuffer_DeleteLastLine(&con);
829 ConBuffer_AddLine(&con, buf, bufpos, mask);
834 ConBuffer_AddLine(&con, buf, bufpos, mask);
838 buf[bufpos++] = *txt;
839 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
841 ConBuffer_AddLine(&con, buf, bufpos, mask);
849 /*! The translation table between the graphical font and plain ASCII --KB */
850 static char qfont_table[256] = {
851 '\0', '#', '#', '#', '#', '.', '#', '#',
852 '#', 9, 10, '#', ' ', 13, '.', '.',
853 '[', ']', '0', '1', '2', '3', '4', '5',
854 '6', '7', '8', '9', '.', '<', '=', '>',
855 ' ', '!', '"', '#', '$', '%', '&', '\'',
856 '(', ')', '*', '+', ',', '-', '.', '/',
857 '0', '1', '2', '3', '4', '5', '6', '7',
858 '8', '9', ':', ';', '<', '=', '>', '?',
859 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
860 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
861 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
862 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
863 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
864 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
865 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
866 'x', 'y', 'z', '{', '|', '}', '~', '<',
868 '<', '=', '>', '#', '#', '.', '#', '#',
869 '#', '#', ' ', '#', ' ', '>', '.', '.',
870 '[', ']', '0', '1', '2', '3', '4', '5',
871 '6', '7', '8', '9', '.', '<', '=', '>',
872 ' ', '!', '"', '#', '$', '%', '&', '\'',
873 '(', ')', '*', '+', ',', '-', '.', '/',
874 '0', '1', '2', '3', '4', '5', '6', '7',
875 '8', '9', ':', ';', '<', '=', '>', '?',
876 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
877 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
878 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
879 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
880 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
881 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
882 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
883 'x', 'y', 'z', '{', '|', '}', '~', '<'
886 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
888 rcon_redirect_sock = sock;
889 rcon_redirect_dest = dest;
890 rcon_redirect_proquakeprotocol = proquakeprotocol;
891 if (rcon_redirect_proquakeprotocol)
893 // reserve space for the packet header
894 rcon_redirect_buffer[0] = 0;
895 rcon_redirect_buffer[1] = 0;
896 rcon_redirect_buffer[2] = 0;
897 rcon_redirect_buffer[3] = 0;
898 // this is a reply to a CCREQ_RCON
899 rcon_redirect_buffer[4] = (char)CCREP_RCON;
902 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
903 rcon_redirect_bufferpos = 5;
906 static void Con_Rcon_Redirect_Flush(void)
908 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
909 if (rcon_redirect_proquakeprotocol)
911 // update the length in the packet header
912 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
914 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
915 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
916 rcon_redirect_bufferpos = 5;
917 rcon_redirect_proquakeprotocol = false;
920 void Con_Rcon_Redirect_End(void)
922 Con_Rcon_Redirect_Flush();
923 rcon_redirect_dest = NULL;
924 rcon_redirect_sock = NULL;
927 void Con_Rcon_Redirect_Abort(void)
929 rcon_redirect_dest = NULL;
930 rcon_redirect_sock = NULL;
938 /// Adds a character to the rcon buffer.
939 static void Con_Rcon_AddChar(int c)
941 if(log_dest_buffer_appending)
943 ++log_dest_buffer_appending;
945 // if this print is in response to an rcon command, add the character
946 // to the rcon redirect buffer
948 if (rcon_redirect_dest)
950 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
951 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
952 Con_Rcon_Redirect_Flush();
954 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
956 if(log_dest_buffer_pos == 0)
957 Log_DestBuffer_Init();
958 log_dest_buffer[log_dest_buffer_pos++] = c;
959 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
960 Log_DestBuffer_Flush_NoLock();
963 log_dest_buffer_pos = 0;
965 --log_dest_buffer_appending;
969 * Convert an RGB color to its nearest quake color.
970 * I'll cheat on this a bit by translating the colors to HSV first,
971 * S and V decide if it's black or white, otherwise, H will decide the
973 * @param _r Red (0-255)
974 * @param _g Green (0-255)
975 * @param _b Blue (0-255)
976 * @return A quake color character.
978 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
980 float r = ((float)_r)/255.0;
981 float g = ((float)_g)/255.0;
982 float b = ((float)_b)/255.0;
983 float min = min(r, min(g, b));
984 float max = max(r, max(g, b));
986 int h; ///< Hue angle [0,360]
987 float s; ///< Saturation [0,1]
988 float v = max; ///< In HSV v == max [0,1]
995 // Saturation threshold. We now say 0.2 is the minimum value for a color!
998 // If the value is less than half, return a black color code.
999 // Otherwise return a white one.
1005 // Let's get the hue angle to define some colors:
1009 h = (int)(60.0 * (g-b)/(max-min))%360;
1011 h = (int)(60.0 * (b-r)/(max-min) + 120);
1012 else // if(max == b) redundant check
1013 h = (int)(60.0 * (r-g)/(max-min) + 240);
1015 if(h < 36) // *red* to orange
1017 else if(h < 80) // orange over *yellow* to evilish-bright-green
1019 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1021 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1023 else if(h < 270) // darkish blue over *dark blue* to cool purple
1025 else if(h < 330) // cool purple over *purple* to ugly swiny red
1027 else // ugly red to red closes the circly
1036 extern cvar_t timestamps;
1037 extern cvar_t timeformat;
1038 extern qboolean sys_nostdout;
1039 void Con_MaskPrint(int additionalmask, const char *msg)
1041 static int mask = 0;
1042 static int index = 0;
1043 static char line[MAX_INPUTLINE];
1046 Thread_LockMutex(con_mutex);
1050 Con_Rcon_AddChar(*msg);
1051 // if this is the beginning of a new line, print timestamp
1054 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1056 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1057 line[index++] = STRING_COLOR_TAG;
1058 // assert( STRING_COLOR_DEFAULT < 10 )
1059 line[index++] = STRING_COLOR_DEFAULT + '0';
1060 // special color codes for chat messages must always come first
1061 // for Con_PrintToHistory to work properly
1062 if (*msg == 1 || *msg == 2)
1067 if (con_chatsound.value)
1069 if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
1071 if(msg[1] == '\r' && cl.foundtalk2wav)
1072 S_LocalSound ("sound/misc/talk2.wav");
1074 S_LocalSound ("sound/misc/talk.wav");
1078 if (msg[1] == '(' && cl.foundtalk2wav)
1079 S_LocalSound ("sound/misc/talk2.wav");
1081 S_LocalSound ("sound/misc/talk.wav");
1084 mask = CON_MASK_CHAT;
1086 line[index++] = STRING_COLOR_TAG;
1087 line[index++] = '3';
1089 Con_Rcon_AddChar(*msg);
1092 for (;*timestamp;index++, timestamp++)
1093 if (index < (int)sizeof(line) - 2)
1094 line[index] = *timestamp;
1096 mask |= additionalmask;
1098 // append the character
1099 line[index++] = *msg;
1100 // if this is a newline character, we have a complete line to print
1101 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1103 // terminate the line
1107 // send to scrollable buffer
1108 if (con_initialized && cls.state != ca_dedicated)
1110 Con_PrintToHistory(line, mask);
1112 // send to terminal or dedicated server window
1114 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1116 if(sys_specialcharactertranslation.integer)
1123 int ch = u8_getchar(p, &q);
1124 if(ch >= 0xE000 && ch <= 0xE0FF)
1126 *p = qfont_table[ch - 0xE000];
1128 memmove(p+1, q, strlen(q)+1);
1136 if(sys_colortranslation.integer == 1) // ANSI
1138 static char printline[MAX_INPUTLINE * 4 + 3];
1139 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1140 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1145 for(in = line, out = printline; *in; ++in)
1149 case STRING_COLOR_TAG:
1150 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1152 char r = tolower(in[2]);
1153 char g = tolower(in[3]);
1154 char b = tolower(in[4]);
1155 // it's a hex digit already, so the else part needs no check --blub
1156 if(isdigit(r)) r -= '0';
1158 if(isdigit(g)) g -= '0';
1160 if(isdigit(b)) b -= '0';
1163 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1164 in += 3; // 3 only, the switch down there does the fourth
1171 case STRING_COLOR_TAG:
1173 *out++ = STRING_COLOR_TAG;
1179 if(lastcolor == 0) break; else lastcolor = 0;
1180 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1185 if(lastcolor == 1) break; else lastcolor = 1;
1186 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1191 if(lastcolor == 2) break; else lastcolor = 2;
1192 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1197 if(lastcolor == 3) break; else lastcolor = 3;
1198 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1203 if(lastcolor == 4) break; else lastcolor = 4;
1204 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1209 if(lastcolor == 5) break; else lastcolor = 5;
1210 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1215 if(lastcolor == 6) break; else lastcolor = 6;
1216 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1221 // bold normal color
1223 if(lastcolor == 8) break; else lastcolor = 8;
1224 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1227 *out++ = STRING_COLOR_TAG;
1234 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1251 Sys_PrintToTerminal(printline);
1253 else if(sys_colortranslation.integer == 2) // Quake
1255 Sys_PrintToTerminal(line);
1259 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1262 for(in = line, out = printline; *in; ++in)
1266 case STRING_COLOR_TAG:
1269 case STRING_COLOR_RGB_TAG_CHAR:
1270 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1275 *out++ = STRING_COLOR_TAG;
1276 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1279 case STRING_COLOR_TAG:
1281 *out++ = STRING_COLOR_TAG;
1296 *out++ = STRING_COLOR_TAG;
1306 Sys_PrintToTerminal(printline);
1309 // empty the line buffer
1316 Thread_UnlockMutex(con_mutex);
1324 void Con_MaskPrintf(int mask, const char *fmt, ...)
1327 char msg[MAX_INPUTLINE];
1329 va_start(argptr,fmt);
1330 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1333 Con_MaskPrint(mask, msg);
1341 void Con_Print(const char *msg)
1343 Con_MaskPrint(CON_MASK_PRINT, msg);
1351 void Con_Printf(const char *fmt, ...)
1354 char msg[MAX_INPUTLINE];
1356 va_start(argptr,fmt);
1357 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1360 Con_MaskPrint(CON_MASK_PRINT, msg);
1368 void Con_DPrint(const char *msg)
1370 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1373 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1381 void Con_DPrintf(const char *fmt, ...)
1384 char msg[MAX_INPUTLINE];
1386 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1389 va_start(argptr,fmt);
1390 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1393 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1398 ==============================================================================
1402 ==============================================================================
1409 The input line scrolls horizontally if typing goes beyond the right edge
1411 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1414 extern cvar_t r_font_disable_freetype;
1415 static void Con_DrawInput (void)
1419 char editlinecopy[MAX_INPUTLINE+1], *text;
1424 if (!key_consoleactive)
1425 return; // don't draw anything
1427 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1428 text = editlinecopy;
1430 // Advanced Console Editing by Radix radix@planetquake.com
1431 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1432 // use strlen of edit_line instead of key_linepos to allow editing
1433 // of early characters w/o erasing
1435 y = (int)strlen(text);
1437 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1438 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1441 // add the cursor frame
1442 if (r_font_disable_freetype.integer)
1444 // this code is freetype incompatible!
1445 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1447 if (!utf8_enable.integer)
1448 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1449 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1451 int ofs = u8_bytelen(text + key_linepos, 1);
1455 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1459 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1460 memcpy(text + key_linepos, curbuf, len);
1463 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1467 // text[key_linepos + 1] = 0;
1469 len_out = key_linepos;
1471 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1472 x = vid_conwidth.value * 0.95 - xo; // scroll
1477 DrawQ_String(x, con_vislines - con_textsize.value*2, text, y + 3, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
1479 // add a cursor on top of this (when using freetype)
1480 if (!r_font_disable_freetype.integer)
1482 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1484 if (!utf8_enable.integer)
1486 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1494 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1495 memcpy(text, curbuf, len);
1498 DrawQ_String(x + xo, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, &col_out, false, FONT_CONSOLE);
1503 // key_line[key_linepos] = 0;
1509 float alignment; // 0 = left, 0.5 = center, 1 = right
1515 const char *continuationString;
1518 int colorindex; // init to -1
1522 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1524 con_text_info_t *ti = (con_text_info_t *) passthrough;
1527 ti->colorindex = -1;
1528 return ti->fontsize * ti->font->maxwidth;
1531 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1532 else if(maxWidth == -1)
1533 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1536 Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1537 // Note: this is NOT a Con_Printf, as it could print recursively
1542 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1548 (void) isContinuation;
1552 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1554 con_text_info_t *ti = (con_text_info_t *) passthrough;
1556 if(ti->y < ti->ymin - 0.001)
1558 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1562 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1563 if(isContinuation && *ti->continuationString)
1564 x = (int) DrawQ_String(x, ti->y, ti->continuationString, strlen(ti->continuationString), ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, ti->font);
1566 DrawQ_String(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
1569 ti->y += ti->fontsize;
1573 static int Con_DrawNotifyRect(int mask_must, int mask_mustnot, float maxage, float x, float y, float width, float height, float fontsize, float alignment_x, float alignment_y, const char *continuationString)
1577 int maxlines = (int) floor(height / fontsize + 0.01f);
1580 int continuationWidth = 0;
1582 double t = cl.time; // saved so it won't change
1585 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1586 ti.fontsize = fontsize;
1587 ti.alignment = alignment_x;
1590 ti.ymax = y + height;
1591 ti.continuationString = continuationString;
1594 Con_WordWidthFunc(&ti, NULL, &l, -1);
1595 l = strlen(continuationString);
1596 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1598 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1599 startidx = CON_LINES_COUNT;
1600 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1602 con_lineinfo_t *l = &CON_LINES(i);
1605 if((l->mask & mask_must) != mask_must)
1607 if(l->mask & mask_mustnot)
1609 if(maxage && (l->addtime < t - maxage))
1613 // Calculate its actual height...
1614 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1615 if(lines + mylines >= maxlines)
1617 nskip = lines + mylines - maxlines;
1626 // then center according to the calculated amount of lines...
1628 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1630 // then actually draw
1631 for(i = startidx; i < CON_LINES_COUNT; ++i)
1633 con_lineinfo_t *l = &CON_LINES(i);
1635 if((l->mask & mask_must) != mask_must)
1637 if(l->mask & mask_mustnot)
1639 if(maxage && (l->addtime < t - maxage))
1642 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1652 Draws the last few lines of output transparently over the game top
1655 void Con_DrawNotify (void)
1658 float chatstart, notifystart, inputsize, height;
1660 char temptext[MAX_INPUTLINE];
1664 if (con_mutex) Thread_LockMutex(con_mutex);
1665 ConBuffer_FixTimes(&con);
1667 numChatlines = con_chat.integer;
1669 chatpos = con_chatpos.integer;
1671 if (con_notify.integer < 0)
1672 Cvar_SetValueQuick(&con_notify, 0);
1673 if (gamemode == GAME_TRANSFUSION)
1674 v = 8; // vertical offset
1678 // GAME_NEXUIZ: center, otherwise left justify
1679 align = con_notifyalign.value;
1680 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1682 if(gamemode == GAME_NEXUIZ)
1686 if(numChatlines || !con_chatrect.integer)
1690 // first chat, input line, then notify
1692 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1694 else if(chatpos > 0)
1696 // first notify, then (chatpos-1) empty lines, then chat, then input
1698 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1700 else // if(chatpos < 0)
1702 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1704 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1709 // just notify and input
1711 chatstart = 0; // shut off gcc warning
1714 v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_INPUT | CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0) | CON_MASK_DEVELOPER, con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1716 if(con_chatrect.integer)
1718 x = con_chatrect_x.value * vid_conwidth.value;
1719 v = con_chatrect_y.value * vid_conheight.value;
1724 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1727 height = numChatlines * con_chatsize.value;
1731 Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, x, v, vid_conwidth.value * con_chatwidth.value, height, con_chatsize.value, 0.0, 1.0, (utf8_enable.integer ? "^3\xee\x80\x8c\xee\x80\x8c\xee\x80\x8c " : "^3\014\014\014 ")); // 015 is ·> character in conchars.tga
1734 if (key_dest == key_message)
1736 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1737 int colorindex = -1;
1740 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16);
1742 // LordHavoc: speedup, and other improvements
1744 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1746 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1748 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1751 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1752 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1754 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1756 if (con_mutex) Thread_UnlockMutex(con_mutex);
1763 Returns the height of a given console line; calculates it if necessary.
1766 static int Con_LineHeight(int lineno)
1768 con_lineinfo_t *li = &CON_LINES(lineno);
1769 if(li->height == -1)
1771 float width = vid_conwidth.value;
1773 con_lineinfo_t *li = &CON_LINES(lineno);
1774 ti.fontsize = con_textsize.value;
1775 ti.font = FONT_CONSOLE;
1776 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1785 Draws a line of the console; returns its height in lines.
1786 If alpha is 0, the line is not drawn, but still wrapped and its height
1790 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1792 float width = vid_conwidth.value;
1794 con_lineinfo_t *li = &CON_LINES(lineno);
1796 if((li->mask & mask_must) != mask_must)
1798 if((li->mask & mask_mustnot) != 0)
1801 ti.continuationString = "";
1803 ti.fontsize = con_textsize.value;
1804 ti.font = FONT_CONSOLE;
1806 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1811 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1818 Calculates the last visible line index and how much to show of it based on
1822 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1827 if(con_backscroll < 0)
1832 // now count until we saw con_backscroll actual lines
1833 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1834 if((CON_LINES(i).mask & mask_must) == mask_must)
1835 if((CON_LINES(i).mask & mask_mustnot) == 0)
1837 int h = Con_LineHeight(i);
1839 // line is the last visible line?
1841 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1843 *limitlast = lines_seen + h - con_backscroll;
1850 // if we get here, no line was on screen - scroll so that one line is
1852 con_backscroll = lines_seen - 1;
1860 Draws the console with the solid background
1861 The typing input line at the bottom should only be drawn if typing is allowed
1864 void Con_DrawConsole (int lines)
1866 float alpha, alpha0;
1869 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1870 cachepic_t *conbackpic;
1875 if (con_mutex) Thread_LockMutex(con_mutex);
1877 if (con_backscroll < 0)
1880 con_vislines = lines;
1882 r_draw2d_force = true;
1884 // draw the background
1885 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1886 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1888 sx = scr_conscroll_x.value;
1889 sy = scr_conscroll_y.value;
1890 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1891 sx *= realtime; sy *= realtime;
1892 sx -= floor(sx); sy -= floor(sy);
1893 if (conbackpic && conbackpic->tex != r_texture_notexture)
1894 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1895 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1896 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1897 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1898 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1901 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1903 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1905 sx = scr_conscroll2_x.value;
1906 sy = scr_conscroll2_y.value;
1907 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1908 sx *= realtime; sy *= realtime;
1909 sx -= floor(sx); sy -= floor(sy);
1910 if(conbackpic && conbackpic->tex != r_texture_notexture)
1911 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1912 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1913 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1914 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1915 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1918 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1920 sx = scr_conscroll3_x.value;
1921 sy = scr_conscroll3_y.value;
1922 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1923 sx *= realtime; sy *= realtime;
1924 sx -= floor(sx); sy -= floor(sy);
1925 if(conbackpic && conbackpic->tex != r_texture_notexture)
1926 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1927 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1928 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1929 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1930 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1933 DrawQ_String(vid_conwidth.integer - DrawQ_TextWidth(engineversion, 0, con_textsize.value, con_textsize.value, false, FONT_CONSOLE), lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE);
1939 int count = CON_LINES_COUNT;
1940 float ymax = con_vislines - 2 * con_textsize.value;
1941 float y = ymax + con_textsize.value * con_backscroll;
1942 for (i = 0;i < count && y >= 0;i++)
1943 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1944 // fix any excessive scrollback for the next frame
1945 if (i >= count && y >= 0)
1947 con_backscroll -= (int)(y / con_textsize.value);
1948 if (con_backscroll < 0)
1953 if(CON_LINES_COUNT > 0)
1955 int i, last, limitlast;
1957 float ymax = con_vislines - 2 * con_textsize.value;
1958 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1959 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1960 y = ymax - con_textsize.value;
1963 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1968 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1970 break; // top of console buffer
1972 break; // top of console window
1979 // draw the input prompt, user text, and cursor if desired
1982 r_draw2d_force = false;
1983 if (con_mutex) Thread_UnlockMutex(con_mutex);
1990 Prints not only map filename, but also
1991 its format (q1/q2/q3/hl) and even its message
1993 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1994 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1995 //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
1996 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1997 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2001 int i, k, max, p, o, min;
2004 unsigned char buf[1024];
2006 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2007 t = FS_Search(message, 1, true);
2010 if (t->numfilenames > 1)
2011 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2012 len = (unsigned char *)Z_Malloc(t->numfilenames);
2014 for(max=i=0;i<t->numfilenames;i++)
2016 k = (int)strlen(t->filenames[i]);
2026 for(i=0;i<t->numfilenames;i++)
2028 int lumpofs = 0, lumplen = 0;
2029 char *entities = NULL;
2030 const char *data = NULL;
2032 char entfilename[MAX_QPATH];
2033 strlcpy(message, "^1**ERROR**^7", sizeof(message));
2035 f = FS_OpenVirtualFile(t->filenames[i], true);
2038 memset(buf, 0, 1024);
2039 FS_Read(f, buf, 1024);
2040 if (!memcmp(buf, "IBSP", 4))
2042 p = LittleLong(((int *)buf)[1]);
2043 if (p == Q3BSPVERSION)
2045 q3dheader_t *header = (q3dheader_t *)buf;
2046 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2047 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2049 else if (p == Q2BSPVERSION)
2051 q2dheader_t *header = (q2dheader_t *)buf;
2052 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2053 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2056 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2058 dheader_t *header = (dheader_t *)buf;
2059 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2060 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2064 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2065 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2066 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2067 if (!entities && lumplen >= 10)
2069 FS_Seek(f, lumpofs, SEEK_SET);
2070 entities = (char *)Z_Malloc(lumplen + 1);
2071 FS_Read(f, entities, lumplen);
2075 // if there are entities to parse, a missing message key just
2076 // means there is no title, so clear the message string now
2082 if (!COM_ParseToken_Simple(&data, false, false, true))
2084 if (com_token[0] == '{')
2086 if (com_token[0] == '}')
2088 // skip leading whitespace
2089 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2090 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2091 keyname[l] = com_token[k+l];
2093 if (!COM_ParseToken_Simple(&data, false, false, true))
2095 if (developer_extra.integer)
2096 Con_DPrintf("key: %s %s\n", keyname, com_token);
2097 if (!strcmp(keyname, "message"))
2099 // get the message contents
2100 strlcpy(message, com_token, sizeof(message));
2110 *(t->filenames[i]+len[i]+5) = 0;
2113 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
2114 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
2115 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
2116 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
2117 default: strlcpy((char *)buf, "??", sizeof(buf));break;
2119 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2124 k = *(t->filenames[0]+5+p);
2127 for(i=1;i<t->numfilenames;i++)
2128 if(*(t->filenames[i]+5+p) != k)
2132 if(p > o && completedname && completednamebufferlength > 0)
2134 memset(completedname, 0, completednamebufferlength);
2135 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2145 New function for tab-completion system
2146 Added by EvilTypeGuy
2147 MEGA Thanks to Taniwha
2150 void Con_DisplayList(const char **list)
2152 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2153 const char **walk = list;
2156 len = (int)strlen(*walk);
2164 len = (int)strlen(*list);
2165 if (pos + maxlen >= width) {
2171 for (i = 0; i < (maxlen - len); i++)
2183 SanitizeString strips color tags from the string in
2184 and writes the result on string out
2186 static void SanitizeString(char *in, char *out)
2190 if(*in == STRING_COLOR_TAG)
2195 out[0] = STRING_COLOR_TAG;
2199 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2206 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2209 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2211 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2218 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2223 else if (*in != STRING_COLOR_TAG)
2226 *out = qfont_table[*(unsigned char*)in];
2233 // Now it becomes TRICKY :D --blub
2234 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2235 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2236 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2237 static int Nicks_offset[MAX_SCOREBOARD]; // when nicks use a space, we need this to move the completion list string starts to avoid invalid memcpys
2238 static int Nicks_matchpos;
2240 // co against <<:BLASTER:>> is true!?
2241 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2245 if(tolower(*a) == tolower(*b))
2259 return (*a < *b) ? -1 : 1;
2263 return (*a < *b) ? -1 : 1;
2267 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2270 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2272 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2273 return Nicks_strncasecmp_nospaces(a, b, a_len);
2274 return strncasecmp(a, b, a_len);
2277 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2279 // ignore non alphanumerics of B
2280 // if A contains a non-alphanumeric, B must contain it as well though!
2283 qboolean alnum_a, alnum_b;
2285 if(tolower(*a) == tolower(*b))
2287 if(*a == 0) // end of both strings, they're equal
2294 // not equal, end of one string?
2299 // ignore non alphanumerics
2300 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2301 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2302 if(!alnum_a) // b must contain this
2303 return (*a < *b) ? -1 : 1;
2306 // otherwise, both are alnum, they're just not equal, return the appropriate number
2308 return (*a < *b) ? -1 : 1;
2314 /* Nicks_CompleteCountPossible
2316 Count the number of possible nicks to complete
2318 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2326 if(!con_nickcompletion.integer)
2329 // changed that to 1
2330 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2333 for(i = 0; i < cl.maxclients; ++i)
2336 if(!cl.scores[p].name[0])
2339 SanitizeString(cl.scores[p].name, name);
2340 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2346 spos = pos - 1; // no need for a minimum of characters :)
2350 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2352 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2353 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2359 if(isCon && spos == 0)
2361 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2367 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2368 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2370 // the sanitized list
2371 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2374 Nicks_matchpos = match;
2377 Nicks_offset[count] = s - (&line[match]);
2378 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2385 static void Cmd_CompleteNicksPrint(int count)
2388 for(i = 0; i < count; ++i)
2389 Con_Printf("%s\n", Nicks_list[i]);
2392 static void Nicks_CutMatchesNormal(int count)
2394 // cut match 0 down to the longest possible completion
2397 c = strlen(Nicks_sanlist[0]) - 1;
2398 for(i = 1; i < count; ++i)
2400 l = strlen(Nicks_sanlist[i]) - 1;
2404 for(l = 0; l <= c; ++l)
2405 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2411 Nicks_sanlist[0][c+1] = 0;
2412 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2415 static unsigned int Nicks_strcleanlen(const char *s)
2420 if( (*s >= 'a' && *s <= 'z') ||
2421 (*s >= 'A' && *s <= 'Z') ||
2422 (*s >= '0' && *s <= '9') ||
2430 static void Nicks_CutMatchesAlphaNumeric(int count)
2432 // cut match 0 down to the longest possible completion
2435 char tempstr[sizeof(Nicks_sanlist[0])];
2437 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2439 c = strlen(Nicks_sanlist[0]);
2440 for(i = 0, l = 0; i < (int)c; ++i)
2442 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2443 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2444 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2446 tempstr[l++] = Nicks_sanlist[0][i];
2451 for(i = 1; i < count; ++i)
2454 b = Nicks_sanlist[i];
2464 if(tolower(*a) == tolower(*b))
2470 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2472 // b is alnum, so cut
2479 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2480 Nicks_CutMatchesNormal(count);
2481 //if(!Nicks_sanlist[0][0])
2482 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2484 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2485 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2489 static void Nicks_CutMatchesNoSpaces(int count)
2491 // cut match 0 down to the longest possible completion
2494 char tempstr[sizeof(Nicks_sanlist[0])];
2497 c = strlen(Nicks_sanlist[0]);
2498 for(i = 0, l = 0; i < (int)c; ++i)
2500 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2502 tempstr[l++] = Nicks_sanlist[0][i];
2507 for(i = 1; i < count; ++i)
2510 b = Nicks_sanlist[i];
2520 if(tolower(*a) == tolower(*b))
2534 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2535 Nicks_CutMatchesNormal(count);
2536 //if(!Nicks_sanlist[0][0])
2537 //Con_Printf("TS: %s\n", tempstr);
2538 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2540 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2541 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2545 static void Nicks_CutMatches(int count)
2547 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2548 Nicks_CutMatchesAlphaNumeric(count);
2549 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2550 Nicks_CutMatchesNoSpaces(count);
2552 Nicks_CutMatchesNormal(count);
2555 static const char **Nicks_CompleteBuildList(int count)
2559 // the list is freed by Con_CompleteCommandLine, so create a char**
2560 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2562 for(; bpos < count; ++bpos)
2563 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2565 Nicks_CutMatches(count);
2573 Restores the previous used color, after the autocompleted name.
2575 static int Nicks_AddLastColor(char *buffer, int pos)
2577 qboolean quote_added = false;
2579 int color = STRING_COLOR_DEFAULT + '0';
2580 char r = 0, g = 0, b = 0;
2582 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2584 // we'll have to add a quote :)
2585 buffer[pos++] = '\"';
2589 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2591 // add color when no quote was added, or when flags &4?
2593 for(match = Nicks_matchpos-1; match >= 0; --match)
2595 if(buffer[match] == STRING_COLOR_TAG)
2597 if( isdigit(buffer[match+1]) )
2599 color = buffer[match+1];
2602 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2604 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2606 r = buffer[match+2];
2607 g = buffer[match+3];
2608 b = buffer[match+4];
2617 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2619 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2620 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2623 buffer[pos++] = STRING_COLOR_TAG;
2626 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2632 buffer[pos++] = color;
2637 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2640 /*if(!con_nickcompletion.integer)
2641 return; is tested in Nicks_CompletionCountPossible */
2642 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2648 msg = Nicks_list[0];
2649 len = min(size - Nicks_matchpos - 3, strlen(msg));
2650 memcpy(&buffer[Nicks_matchpos], msg, len);
2651 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2652 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2653 buffer[len++] = ' ';
2660 Con_Printf("\n%i possible nicks:\n", n);
2661 Cmd_CompleteNicksPrint(n);
2663 Nicks_CutMatches(n);
2665 msg = Nicks_sanlist[0];
2666 len = min(size - Nicks_matchpos, strlen(msg));
2667 memcpy(&buffer[Nicks_matchpos], msg, len);
2668 buffer[Nicks_matchpos + len] = 0;
2670 return Nicks_matchpos + len;
2677 Con_CompleteCommandLine
2679 New function for tab-completion system
2680 Added by EvilTypeGuy
2681 Thanks to Fett erich@heintz.com
2683 Enhanced to tab-complete map names by [515]
2686 void Con_CompleteCommandLine (void)
2688 const char *cmd = "";
2690 const char **list[4] = {0, 0, 0, 0};
2693 int c, v, a, i, cmd_len, pos, k;
2694 int n; // nicks --blub
2695 const char *space, *patterns;
2698 //find what we want to complete
2703 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2709 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2710 key_line[key_linepos] = 0; //hide them
2712 space = strchr(key_line + 1, ' ');
2713 if(space && pos == (space - key_line) + 1)
2715 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2717 patterns = Cvar_VariableString(va(vabuf, sizeof(vabuf), "con_completion_%s", command)); // TODO maybe use a better place for this?
2718 if(patterns && !*patterns)
2719 patterns = NULL; // get rid of the empty string
2721 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2725 if (GetMapList(s, t, sizeof(t)))
2727 // first move the cursor
2728 key_linepos += (int)strlen(t) - (int)strlen(s);
2730 // and now do the actual work
2732 strlcat(key_line, t, MAX_INPUTLINE);
2733 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2735 // and fix the cursor
2736 if(key_linepos > (int) strlen(key_line))
2737 key_linepos = (int) strlen(key_line);
2746 stringlist_t resultbuf, dirbuf;
2749 // // store completion patterns (space separated) for command foo in con_completion_foo
2750 // set con_completion_foo "foodata/*.foodefault *.foo"
2753 // Note: patterns with slash are always treated as absolute
2754 // patterns; patterns without slash search in the innermost
2755 // directory the user specified. There is no way to "complete into"
2756 // a directory as of now, as directories seem to be unknown to the
2760 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2761 // set con_completion_playdemo "*.dem"
2762 // set con_completion_play "*.wav *.ogg"
2764 // TODO somehow add support for directories; these shall complete
2765 // to their name + an appended slash.
2767 stringlistinit(&resultbuf);
2768 stringlistinit(&dirbuf);
2769 while(COM_ParseToken_Simple(&patterns, false, false, true))
2772 if(strchr(com_token, '/'))
2774 search = FS_Search(com_token, true, true);
2778 const char *slash = strrchr(s, '/');
2781 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2782 strlcat(t, com_token, sizeof(t));
2783 search = FS_Search(t, true, true);
2786 search = FS_Search(com_token, true, true);
2790 for(i = 0; i < search->numfilenames; ++i)
2791 if(!strncmp(search->filenames[i], s, strlen(s)))
2792 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2793 stringlistappend(&resultbuf, search->filenames[i]);
2794 FS_FreeSearch(search);
2798 // In any case, add directory names
2801 const char *slash = strrchr(s, '/');
2804 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2805 strlcat(t, "*", sizeof(t));
2806 search = FS_Search(t, true, true);
2809 search = FS_Search("*", true, true);
2812 for(i = 0; i < search->numfilenames; ++i)
2813 if(!strncmp(search->filenames[i], s, strlen(s)))
2814 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2815 stringlistappend(&dirbuf, search->filenames[i]);
2816 FS_FreeSearch(search);
2820 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2823 unsigned int matchchars;
2824 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2826 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2829 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2831 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2835 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2836 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2837 for(i = 0; i < dirbuf.numstrings; ++i)
2839 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2841 for(i = 0; i < resultbuf.numstrings; ++i)
2843 Con_Printf("%s\n", resultbuf.strings[i]);
2845 matchchars = sizeof(t) - 1;
2846 if(resultbuf.numstrings > 0)
2848 p = resultbuf.strings[0];
2849 q = resultbuf.strings[resultbuf.numstrings - 1];
2850 for(; *p && *p == *q; ++p, ++q);
2851 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2853 if(dirbuf.numstrings > 0)
2855 p = dirbuf.strings[0];
2856 q = dirbuf.strings[dirbuf.numstrings - 1];
2857 for(; *p && *p == *q; ++p, ++q);
2858 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2860 // now p points to the first non-equal character, or to the end
2861 // of resultbuf.strings[0]. We want to append the characters
2862 // from resultbuf.strings[0] to (not including) p as these are
2863 // the unique prefix
2864 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2867 // first move the cursor
2868 key_linepos += (int)strlen(t) - (int)strlen(s);
2870 // and now do the actual work
2872 strlcat(key_line, t, MAX_INPUTLINE);
2873 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2875 // and fix the cursor
2876 if(key_linepos > (int) strlen(key_line))
2877 key_linepos = (int) strlen(key_line);
2879 stringlistfreecontents(&resultbuf);
2880 stringlistfreecontents(&dirbuf);
2882 return; // bail out, when we complete for a command that wants a file name
2887 // Count number of possible matches and print them
2888 c = Cmd_CompleteCountPossible(s);
2891 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2892 Cmd_CompleteCommandPrint(s);
2894 v = Cvar_CompleteCountPossible(s);
2897 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2898 Cvar_CompleteCvarPrint(s);
2900 a = Cmd_CompleteAliasCountPossible(s);
2903 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2904 Cmd_CompleteAliasPrint(s);
2906 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2909 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2910 Cmd_CompleteNicksPrint(n);
2913 if (!(c + v + a + n)) // No possible matches
2916 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2921 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2923 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2925 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2927 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2929 for (cmd_len = (int)strlen(s);;cmd_len++)
2932 for (i = 0; i < 3; i++)
2934 for (l = list[i];*l;l++)
2935 if ((*l)[cmd_len] != cmd[cmd_len])
2937 // all possible matches share this character, so we continue...
2940 // if all matches ended at the same position, stop
2941 // (this means there is only one match)
2947 // prevent a buffer overrun by limiting cmd_len according to remaining space
2948 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2952 memcpy(&key_line[key_linepos], cmd, cmd_len);
2953 key_linepos += cmd_len;
2954 // if there is only one match, add a space after it
2955 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2958 { // was a nick, might have an offset, and needs colors ;) --blub
2959 key_linepos = pos - Nicks_offset[0];
2960 cmd_len = strlen(Nicks_list[0]);
2961 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2963 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2964 key_linepos += cmd_len;
2965 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2966 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2968 key_line[key_linepos++] = ' ';
2972 // use strlcat to avoid a buffer overrun
2973 key_line[key_linepos] = 0;
2974 strlcat(key_line, s2, sizeof(key_line));
2976 // free the command, cvar, and alias lists
2977 for (i = 0; i < 4; i++)
2979 Mem_Free((void *)list[i]);