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 if(rcon_redirect_sock)
910 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
911 if (rcon_redirect_proquakeprotocol)
913 // update the length in the packet header
914 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
916 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
918 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
919 rcon_redirect_bufferpos = 5;
920 rcon_redirect_proquakeprotocol = false;
923 void Con_Rcon_Redirect_End(void)
925 Con_Rcon_Redirect_Flush();
926 rcon_redirect_dest = NULL;
927 rcon_redirect_sock = NULL;
930 void Con_Rcon_Redirect_Abort(void)
932 rcon_redirect_dest = NULL;
933 rcon_redirect_sock = NULL;
941 /// Adds a character to the rcon buffer.
942 static void Con_Rcon_AddChar(int c)
944 if(log_dest_buffer_appending)
946 ++log_dest_buffer_appending;
948 // if this print is in response to an rcon command, add the character
949 // to the rcon redirect buffer
951 if (rcon_redirect_dest)
953 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
954 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
955 Con_Rcon_Redirect_Flush();
957 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
959 if(log_dest_buffer_pos == 0)
960 Log_DestBuffer_Init();
961 log_dest_buffer[log_dest_buffer_pos++] = c;
962 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
963 Log_DestBuffer_Flush_NoLock();
966 log_dest_buffer_pos = 0;
968 --log_dest_buffer_appending;
972 * Convert an RGB color to its nearest quake color.
973 * I'll cheat on this a bit by translating the colors to HSV first,
974 * S and V decide if it's black or white, otherwise, H will decide the
976 * @param _r Red (0-255)
977 * @param _g Green (0-255)
978 * @param _b Blue (0-255)
979 * @return A quake color character.
981 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
983 float r = ((float)_r)/255.0;
984 float g = ((float)_g)/255.0;
985 float b = ((float)_b)/255.0;
986 float min = min(r, min(g, b));
987 float max = max(r, max(g, b));
989 int h; ///< Hue angle [0,360]
990 float s; ///< Saturation [0,1]
991 float v = max; ///< In HSV v == max [0,1]
998 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1001 // If the value is less than half, return a black color code.
1002 // Otherwise return a white one.
1008 // Let's get the hue angle to define some colors:
1012 h = (int)(60.0 * (g-b)/(max-min))%360;
1014 h = (int)(60.0 * (b-r)/(max-min) + 120);
1015 else // if(max == b) redundant check
1016 h = (int)(60.0 * (r-g)/(max-min) + 240);
1018 if(h < 36) // *red* to orange
1020 else if(h < 80) // orange over *yellow* to evilish-bright-green
1022 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1024 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1026 else if(h < 270) // darkish blue over *dark blue* to cool purple
1028 else if(h < 330) // cool purple over *purple* to ugly swiny red
1030 else // ugly red to red closes the circly
1039 extern cvar_t timestamps;
1040 extern cvar_t timeformat;
1041 extern qboolean sys_nostdout;
1042 void Con_MaskPrint(int additionalmask, const char *msg)
1044 static int mask = 0;
1045 static int index = 0;
1046 static char line[MAX_INPUTLINE];
1049 Thread_LockMutex(con_mutex);
1053 Con_Rcon_AddChar(*msg);
1054 // if this is the beginning of a new line, print timestamp
1057 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1059 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1060 line[index++] = STRING_COLOR_TAG;
1061 // assert( STRING_COLOR_DEFAULT < 10 )
1062 line[index++] = STRING_COLOR_DEFAULT + '0';
1063 // special color codes for chat messages must always come first
1064 // for Con_PrintToHistory to work properly
1065 if (*msg == 1 || *msg == 2)
1070 if (con_chatsound.value)
1072 if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
1074 if(msg[1] == '\r' && cl.foundtalk2wav)
1075 S_LocalSound ("sound/misc/talk2.wav");
1077 S_LocalSound ("sound/misc/talk.wav");
1081 if (msg[1] == '(' && cl.foundtalk2wav)
1082 S_LocalSound ("sound/misc/talk2.wav");
1084 S_LocalSound ("sound/misc/talk.wav");
1087 mask = CON_MASK_CHAT;
1089 line[index++] = STRING_COLOR_TAG;
1090 line[index++] = '3';
1092 Con_Rcon_AddChar(*msg);
1095 for (;*timestamp;index++, timestamp++)
1096 if (index < (int)sizeof(line) - 2)
1097 line[index] = *timestamp;
1099 mask |= additionalmask;
1101 // append the character
1102 line[index++] = *msg;
1103 // if this is a newline character, we have a complete line to print
1104 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1106 // terminate the line
1110 // send to scrollable buffer
1111 if (con_initialized && cls.state != ca_dedicated)
1113 Con_PrintToHistory(line, mask);
1115 // send to terminal or dedicated server window
1117 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1119 if(sys_specialcharactertranslation.integer)
1126 int ch = u8_getchar(p, &q);
1127 if(ch >= 0xE000 && ch <= 0xE0FF)
1129 *p = qfont_table[ch - 0xE000];
1131 memmove(p+1, q, strlen(q)+1);
1139 if(sys_colortranslation.integer == 1) // ANSI
1141 static char printline[MAX_INPUTLINE * 4 + 3];
1142 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1143 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1148 for(in = line, out = printline; *in; ++in)
1152 case STRING_COLOR_TAG:
1153 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1155 char r = tolower(in[2]);
1156 char g = tolower(in[3]);
1157 char b = tolower(in[4]);
1158 // it's a hex digit already, so the else part needs no check --blub
1159 if(isdigit(r)) r -= '0';
1161 if(isdigit(g)) g -= '0';
1163 if(isdigit(b)) b -= '0';
1166 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1167 in += 3; // 3 only, the switch down there does the fourth
1174 case STRING_COLOR_TAG:
1176 *out++ = STRING_COLOR_TAG;
1182 if(lastcolor == 0) break; else lastcolor = 0;
1183 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1188 if(lastcolor == 1) break; else lastcolor = 1;
1189 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1194 if(lastcolor == 2) break; else lastcolor = 2;
1195 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1200 if(lastcolor == 3) break; else lastcolor = 3;
1201 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1206 if(lastcolor == 4) break; else lastcolor = 4;
1207 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1212 if(lastcolor == 5) break; else lastcolor = 5;
1213 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1218 if(lastcolor == 6) break; else lastcolor = 6;
1219 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1224 // bold normal color
1226 if(lastcolor == 8) break; else lastcolor = 8;
1227 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1230 *out++ = STRING_COLOR_TAG;
1237 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1254 Sys_PrintToTerminal(printline);
1256 else if(sys_colortranslation.integer == 2) // Quake
1258 Sys_PrintToTerminal(line);
1262 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1265 for(in = line, out = printline; *in; ++in)
1269 case STRING_COLOR_TAG:
1272 case STRING_COLOR_RGB_TAG_CHAR:
1273 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1278 *out++ = STRING_COLOR_TAG;
1279 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1282 case STRING_COLOR_TAG:
1284 *out++ = STRING_COLOR_TAG;
1299 *out++ = STRING_COLOR_TAG;
1309 Sys_PrintToTerminal(printline);
1312 // empty the line buffer
1319 Thread_UnlockMutex(con_mutex);
1327 void Con_MaskPrintf(int mask, const char *fmt, ...)
1330 char msg[MAX_INPUTLINE];
1332 va_start(argptr,fmt);
1333 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1336 Con_MaskPrint(mask, msg);
1344 void Con_Print(const char *msg)
1346 Con_MaskPrint(CON_MASK_PRINT, msg);
1354 void Con_Printf(const char *fmt, ...)
1357 char msg[MAX_INPUTLINE];
1359 va_start(argptr,fmt);
1360 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1363 Con_MaskPrint(CON_MASK_PRINT, msg);
1371 void Con_DPrint(const char *msg)
1373 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1376 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1384 void Con_DPrintf(const char *fmt, ...)
1387 char msg[MAX_INPUTLINE];
1389 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1392 va_start(argptr,fmt);
1393 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1396 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1401 ==============================================================================
1405 ==============================================================================
1412 The input line scrolls horizontally if typing goes beyond the right edge
1414 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1417 extern cvar_t r_font_disable_freetype;
1418 static void Con_DrawInput (void)
1422 char editlinecopy[MAX_INPUTLINE+1], *text;
1427 if (!key_consoleactive)
1428 return; // don't draw anything
1430 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1431 text = editlinecopy;
1433 // Advanced Console Editing by Radix radix@planetquake.com
1434 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1435 // use strlen of edit_line instead of key_linepos to allow editing
1436 // of early characters w/o erasing
1438 y = (int)strlen(text);
1440 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1441 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1444 // add the cursor frame
1445 if (r_font_disable_freetype.integer)
1447 // this code is freetype incompatible!
1448 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1450 if (!utf8_enable.integer)
1451 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1452 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1454 int ofs = u8_bytelen(text + key_linepos, 1);
1458 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1462 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1463 memcpy(text + key_linepos, curbuf, len);
1466 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1470 // text[key_linepos + 1] = 0;
1472 len_out = key_linepos;
1474 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1475 x = vid_conwidth.value * 0.95 - xo; // scroll
1480 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 );
1482 // add a cursor on top of this (when using freetype)
1483 if (!r_font_disable_freetype.integer)
1485 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1487 if (!utf8_enable.integer)
1489 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1497 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1498 memcpy(text, curbuf, len);
1501 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);
1506 // key_line[key_linepos] = 0;
1512 float alignment; // 0 = left, 0.5 = center, 1 = right
1518 const char *continuationString;
1521 int colorindex; // init to -1
1525 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1527 con_text_info_t *ti = (con_text_info_t *) passthrough;
1530 ti->colorindex = -1;
1531 return ti->fontsize * ti->font->maxwidth;
1534 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1535 else if(maxWidth == -1)
1536 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1539 Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1540 // Note: this is NOT a Con_Printf, as it could print recursively
1545 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1551 (void) isContinuation;
1555 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1557 con_text_info_t *ti = (con_text_info_t *) passthrough;
1559 if(ti->y < ti->ymin - 0.001)
1561 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1565 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1566 if(isContinuation && *ti->continuationString)
1567 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);
1569 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);
1572 ti->y += ti->fontsize;
1576 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)
1580 int maxlines = (int) floor(height / fontsize + 0.01f);
1583 int continuationWidth = 0;
1585 double t = cl.time; // saved so it won't change
1588 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1589 ti.fontsize = fontsize;
1590 ti.alignment = alignment_x;
1593 ti.ymax = y + height;
1594 ti.continuationString = continuationString;
1597 Con_WordWidthFunc(&ti, NULL, &l, -1);
1598 l = strlen(continuationString);
1599 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1601 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1602 startidx = CON_LINES_COUNT;
1603 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1605 con_lineinfo_t *l = &CON_LINES(i);
1608 if((l->mask & mask_must) != mask_must)
1610 if(l->mask & mask_mustnot)
1612 if(maxage && (l->addtime < t - maxage))
1616 // Calculate its actual height...
1617 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1618 if(lines + mylines >= maxlines)
1620 nskip = lines + mylines - maxlines;
1629 // then center according to the calculated amount of lines...
1631 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1633 // then actually draw
1634 for(i = startidx; i < CON_LINES_COUNT; ++i)
1636 con_lineinfo_t *l = &CON_LINES(i);
1638 if((l->mask & mask_must) != mask_must)
1640 if(l->mask & mask_mustnot)
1642 if(maxage && (l->addtime < t - maxage))
1645 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1655 Draws the last few lines of output transparently over the game top
1658 void Con_DrawNotify (void)
1661 float chatstart, notifystart, inputsize, height;
1663 char temptext[MAX_INPUTLINE];
1667 if (con_mutex) Thread_LockMutex(con_mutex);
1668 ConBuffer_FixTimes(&con);
1670 numChatlines = con_chat.integer;
1672 chatpos = con_chatpos.integer;
1674 if (con_notify.integer < 0)
1675 Cvar_SetValueQuick(&con_notify, 0);
1676 if (gamemode == GAME_TRANSFUSION)
1677 v = 8; // vertical offset
1681 // GAME_NEXUIZ: center, otherwise left justify
1682 align = con_notifyalign.value;
1683 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1685 if(gamemode == GAME_NEXUIZ)
1689 if(numChatlines || !con_chatrect.integer)
1693 // first chat, input line, then notify
1695 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1697 else if(chatpos > 0)
1699 // first notify, then (chatpos-1) empty lines, then chat, then input
1701 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1703 else // if(chatpos < 0)
1705 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1707 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1712 // just notify and input
1714 chatstart = 0; // shut off gcc warning
1717 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, "");
1719 if(con_chatrect.integer)
1721 x = con_chatrect_x.value * vid_conwidth.value;
1722 v = con_chatrect_y.value * vid_conheight.value;
1727 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1730 height = numChatlines * con_chatsize.value;
1734 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
1737 if (key_dest == key_message)
1739 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1740 int colorindex = -1;
1743 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16);
1745 // LordHavoc: speedup, and other improvements
1747 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1749 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1751 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1754 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1755 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1757 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1759 if (con_mutex) Thread_UnlockMutex(con_mutex);
1766 Returns the height of a given console line; calculates it if necessary.
1769 static int Con_LineHeight(int lineno)
1771 con_lineinfo_t *li = &CON_LINES(lineno);
1772 if(li->height == -1)
1774 float width = vid_conwidth.value;
1776 con_lineinfo_t *li = &CON_LINES(lineno);
1777 ti.fontsize = con_textsize.value;
1778 ti.font = FONT_CONSOLE;
1779 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1788 Draws a line of the console; returns its height in lines.
1789 If alpha is 0, the line is not drawn, but still wrapped and its height
1793 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1795 float width = vid_conwidth.value;
1797 con_lineinfo_t *li = &CON_LINES(lineno);
1799 if((li->mask & mask_must) != mask_must)
1801 if((li->mask & mask_mustnot) != 0)
1804 ti.continuationString = "";
1806 ti.fontsize = con_textsize.value;
1807 ti.font = FONT_CONSOLE;
1809 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1814 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1821 Calculates the last visible line index and how much to show of it based on
1825 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1830 if(con_backscroll < 0)
1835 // now count until we saw con_backscroll actual lines
1836 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1837 if((CON_LINES(i).mask & mask_must) == mask_must)
1838 if((CON_LINES(i).mask & mask_mustnot) == 0)
1840 int h = Con_LineHeight(i);
1842 // line is the last visible line?
1844 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1846 *limitlast = lines_seen + h - con_backscroll;
1853 // if we get here, no line was on screen - scroll so that one line is
1855 con_backscroll = lines_seen - 1;
1863 Draws the console with the solid background
1864 The typing input line at the bottom should only be drawn if typing is allowed
1867 void Con_DrawConsole (int lines)
1869 float alpha, alpha0;
1872 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1873 cachepic_t *conbackpic;
1878 if (con_mutex) Thread_LockMutex(con_mutex);
1880 if (con_backscroll < 0)
1883 con_vislines = lines;
1885 r_draw2d_force = true;
1887 // draw the background
1888 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1889 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1891 sx = scr_conscroll_x.value;
1892 sy = scr_conscroll_y.value;
1893 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1894 sx *= realtime; sy *= realtime;
1895 sx -= floor(sx); sy -= floor(sy);
1896 if (conbackpic && conbackpic->tex != r_texture_notexture)
1897 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1898 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1899 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1900 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1901 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1904 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1906 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1908 sx = scr_conscroll2_x.value;
1909 sy = scr_conscroll2_y.value;
1910 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1911 sx *= realtime; sy *= realtime;
1912 sx -= floor(sx); sy -= floor(sy);
1913 if(conbackpic && conbackpic->tex != r_texture_notexture)
1914 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1915 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1916 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1917 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1918 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1921 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1923 sx = scr_conscroll3_x.value;
1924 sy = scr_conscroll3_y.value;
1925 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1926 sx *= realtime; sy *= realtime;
1927 sx -= floor(sx); sy -= floor(sy);
1928 if(conbackpic && conbackpic->tex != r_texture_notexture)
1929 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1930 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1931 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1932 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1933 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1936 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);
1942 int count = CON_LINES_COUNT;
1943 float ymax = con_vislines - 2 * con_textsize.value;
1944 float y = ymax + con_textsize.value * con_backscroll;
1945 for (i = 0;i < count && y >= 0;i++)
1946 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1947 // fix any excessive scrollback for the next frame
1948 if (i >= count && y >= 0)
1950 con_backscroll -= (int)(y / con_textsize.value);
1951 if (con_backscroll < 0)
1956 if(CON_LINES_COUNT > 0)
1958 int i, last, limitlast;
1960 float ymax = con_vislines - 2 * con_textsize.value;
1961 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1962 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1963 y = ymax - con_textsize.value;
1966 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1971 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1973 break; // top of console buffer
1975 break; // top of console window
1982 // draw the input prompt, user text, and cursor if desired
1985 r_draw2d_force = false;
1986 if (con_mutex) Thread_UnlockMutex(con_mutex);
1993 Prints not only map filename, but also
1994 its format (q1/q2/q3/hl) and even its message
1996 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1997 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1998 //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
1999 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2000 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2004 int i, k, max, p, o, min;
2007 unsigned char buf[1024];
2009 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2010 t = FS_Search(message, 1, true);
2013 if (t->numfilenames > 1)
2014 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2015 len = (unsigned char *)Z_Malloc(t->numfilenames);
2017 for(max=i=0;i<t->numfilenames;i++)
2019 k = (int)strlen(t->filenames[i]);
2029 for(i=0;i<t->numfilenames;i++)
2031 int lumpofs = 0, lumplen = 0;
2032 char *entities = NULL;
2033 const char *data = NULL;
2035 char entfilename[MAX_QPATH];
2036 strlcpy(message, "^1**ERROR**^7", sizeof(message));
2038 f = FS_OpenVirtualFile(t->filenames[i], true);
2041 memset(buf, 0, 1024);
2042 FS_Read(f, buf, 1024);
2043 if (!memcmp(buf, "IBSP", 4))
2045 p = LittleLong(((int *)buf)[1]);
2046 if (p == Q3BSPVERSION)
2048 q3dheader_t *header = (q3dheader_t *)buf;
2049 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2050 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2052 else if (p == Q2BSPVERSION)
2054 q2dheader_t *header = (q2dheader_t *)buf;
2055 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2056 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2059 else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2061 dheader_t *header = (dheader_t *)buf;
2062 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2063 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2067 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2068 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2069 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2070 if (!entities && lumplen >= 10)
2072 FS_Seek(f, lumpofs, SEEK_SET);
2073 entities = (char *)Z_Malloc(lumplen + 1);
2074 FS_Read(f, entities, lumplen);
2078 // if there are entities to parse, a missing message key just
2079 // means there is no title, so clear the message string now
2085 if (!COM_ParseToken_Simple(&data, false, false, true))
2087 if (com_token[0] == '{')
2089 if (com_token[0] == '}')
2091 // skip leading whitespace
2092 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2093 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2094 keyname[l] = com_token[k+l];
2096 if (!COM_ParseToken_Simple(&data, false, false, true))
2098 if (developer_extra.integer)
2099 Con_DPrintf("key: %s %s\n", keyname, com_token);
2100 if (!strcmp(keyname, "message"))
2102 // get the message contents
2103 strlcpy(message, com_token, sizeof(message));
2113 *(t->filenames[i]+len[i]+5) = 0;
2116 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
2117 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
2118 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
2119 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
2120 default: strlcpy((char *)buf, "??", sizeof(buf));break;
2122 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2127 k = *(t->filenames[0]+5+p);
2130 for(i=1;i<t->numfilenames;i++)
2131 if(*(t->filenames[i]+5+p) != k)
2135 if(p > o && completedname && completednamebufferlength > 0)
2137 memset(completedname, 0, completednamebufferlength);
2138 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2148 New function for tab-completion system
2149 Added by EvilTypeGuy
2150 MEGA Thanks to Taniwha
2153 void Con_DisplayList(const char **list)
2155 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2156 const char **walk = list;
2159 len = (int)strlen(*walk);
2167 len = (int)strlen(*list);
2168 if (pos + maxlen >= width) {
2174 for (i = 0; i < (maxlen - len); i++)
2186 SanitizeString strips color tags from the string in
2187 and writes the result on string out
2189 static void SanitizeString(char *in, char *out)
2193 if(*in == STRING_COLOR_TAG)
2198 out[0] = STRING_COLOR_TAG;
2202 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2209 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2212 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2214 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2221 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2226 else if (*in != STRING_COLOR_TAG)
2229 *out = qfont_table[*(unsigned char*)in];
2236 // Now it becomes TRICKY :D --blub
2237 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2238 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2239 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2240 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
2241 static int Nicks_matchpos;
2243 // co against <<:BLASTER:>> is true!?
2244 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2248 if(tolower(*a) == tolower(*b))
2262 return (*a < *b) ? -1 : 1;
2266 return (*a < *b) ? -1 : 1;
2270 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2273 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2275 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2276 return Nicks_strncasecmp_nospaces(a, b, a_len);
2277 return strncasecmp(a, b, a_len);
2280 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2282 // ignore non alphanumerics of B
2283 // if A contains a non-alphanumeric, B must contain it as well though!
2286 qboolean alnum_a, alnum_b;
2288 if(tolower(*a) == tolower(*b))
2290 if(*a == 0) // end of both strings, they're equal
2297 // not equal, end of one string?
2302 // ignore non alphanumerics
2303 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2304 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2305 if(!alnum_a) // b must contain this
2306 return (*a < *b) ? -1 : 1;
2309 // otherwise, both are alnum, they're just not equal, return the appropriate number
2311 return (*a < *b) ? -1 : 1;
2317 /* Nicks_CompleteCountPossible
2319 Count the number of possible nicks to complete
2321 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2329 if(!con_nickcompletion.integer)
2332 // changed that to 1
2333 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2336 for(i = 0; i < cl.maxclients; ++i)
2339 if(!cl.scores[p].name[0])
2342 SanitizeString(cl.scores[p].name, name);
2343 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2349 spos = pos - 1; // no need for a minimum of characters :)
2353 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2355 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2356 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2362 if(isCon && spos == 0)
2364 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2370 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2371 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2373 // the sanitized list
2374 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2377 Nicks_matchpos = match;
2380 Nicks_offset[count] = s - (&line[match]);
2381 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2388 static void Cmd_CompleteNicksPrint(int count)
2391 for(i = 0; i < count; ++i)
2392 Con_Printf("%s\n", Nicks_list[i]);
2395 static void Nicks_CutMatchesNormal(int count)
2397 // cut match 0 down to the longest possible completion
2400 c = strlen(Nicks_sanlist[0]) - 1;
2401 for(i = 1; i < count; ++i)
2403 l = strlen(Nicks_sanlist[i]) - 1;
2407 for(l = 0; l <= c; ++l)
2408 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2414 Nicks_sanlist[0][c+1] = 0;
2415 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2418 static unsigned int Nicks_strcleanlen(const char *s)
2423 if( (*s >= 'a' && *s <= 'z') ||
2424 (*s >= 'A' && *s <= 'Z') ||
2425 (*s >= '0' && *s <= '9') ||
2433 static void Nicks_CutMatchesAlphaNumeric(int count)
2435 // cut match 0 down to the longest possible completion
2438 char tempstr[sizeof(Nicks_sanlist[0])];
2440 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2442 c = strlen(Nicks_sanlist[0]);
2443 for(i = 0, l = 0; i < (int)c; ++i)
2445 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2446 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2447 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2449 tempstr[l++] = Nicks_sanlist[0][i];
2454 for(i = 1; i < count; ++i)
2457 b = Nicks_sanlist[i];
2467 if(tolower(*a) == tolower(*b))
2473 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2475 // b is alnum, so cut
2482 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2483 Nicks_CutMatchesNormal(count);
2484 //if(!Nicks_sanlist[0][0])
2485 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2487 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2488 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2492 static void Nicks_CutMatchesNoSpaces(int count)
2494 // cut match 0 down to the longest possible completion
2497 char tempstr[sizeof(Nicks_sanlist[0])];
2500 c = strlen(Nicks_sanlist[0]);
2501 for(i = 0, l = 0; i < (int)c; ++i)
2503 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2505 tempstr[l++] = Nicks_sanlist[0][i];
2510 for(i = 1; i < count; ++i)
2513 b = Nicks_sanlist[i];
2523 if(tolower(*a) == tolower(*b))
2537 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2538 Nicks_CutMatchesNormal(count);
2539 //if(!Nicks_sanlist[0][0])
2540 //Con_Printf("TS: %s\n", tempstr);
2541 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2543 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2544 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2548 static void Nicks_CutMatches(int count)
2550 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2551 Nicks_CutMatchesAlphaNumeric(count);
2552 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2553 Nicks_CutMatchesNoSpaces(count);
2555 Nicks_CutMatchesNormal(count);
2558 static const char **Nicks_CompleteBuildList(int count)
2562 // the list is freed by Con_CompleteCommandLine, so create a char**
2563 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2565 for(; bpos < count; ++bpos)
2566 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2568 Nicks_CutMatches(count);
2576 Restores the previous used color, after the autocompleted name.
2578 static int Nicks_AddLastColor(char *buffer, int pos)
2580 qboolean quote_added = false;
2582 int color = STRING_COLOR_DEFAULT + '0';
2583 char r = 0, g = 0, b = 0;
2585 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2587 // we'll have to add a quote :)
2588 buffer[pos++] = '\"';
2592 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2594 // add color when no quote was added, or when flags &4?
2596 for(match = Nicks_matchpos-1; match >= 0; --match)
2598 if(buffer[match] == STRING_COLOR_TAG)
2600 if( isdigit(buffer[match+1]) )
2602 color = buffer[match+1];
2605 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2607 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2609 r = buffer[match+2];
2610 g = buffer[match+3];
2611 b = buffer[match+4];
2620 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2622 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2623 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2626 buffer[pos++] = STRING_COLOR_TAG;
2629 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2635 buffer[pos++] = color;
2640 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2643 /*if(!con_nickcompletion.integer)
2644 return; is tested in Nicks_CompletionCountPossible */
2645 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2651 msg = Nicks_list[0];
2652 len = min(size - Nicks_matchpos - 3, strlen(msg));
2653 memcpy(&buffer[Nicks_matchpos], msg, len);
2654 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2655 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2656 buffer[len++] = ' ';
2663 Con_Printf("\n%i possible nicks:\n", n);
2664 Cmd_CompleteNicksPrint(n);
2666 Nicks_CutMatches(n);
2668 msg = Nicks_sanlist[0];
2669 len = min(size - Nicks_matchpos, strlen(msg));
2670 memcpy(&buffer[Nicks_matchpos], msg, len);
2671 buffer[Nicks_matchpos + len] = 0;
2673 return Nicks_matchpos + len;
2680 Con_CompleteCommandLine
2682 New function for tab-completion system
2683 Added by EvilTypeGuy
2684 Thanks to Fett erich@heintz.com
2686 Enhanced to tab-complete map names by [515]
2689 void Con_CompleteCommandLine (void)
2691 const char *cmd = "";
2693 const char **list[4] = {0, 0, 0, 0};
2696 int c, v, a, i, cmd_len, pos, k;
2697 int n; // nicks --blub
2698 const char *space, *patterns;
2701 //find what we want to complete
2706 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2712 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2713 key_line[key_linepos] = 0; //hide them
2715 space = strchr(key_line + 1, ' ');
2716 if(space && pos == (space - key_line) + 1)
2718 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2720 patterns = Cvar_VariableString(va(vabuf, sizeof(vabuf), "con_completion_%s", command)); // TODO maybe use a better place for this?
2721 if(patterns && !*patterns)
2722 patterns = NULL; // get rid of the empty string
2724 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2728 if (GetMapList(s, t, sizeof(t)))
2730 // first move the cursor
2731 key_linepos += (int)strlen(t) - (int)strlen(s);
2733 // and now do the actual work
2735 strlcat(key_line, t, MAX_INPUTLINE);
2736 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2738 // and fix the cursor
2739 if(key_linepos > (int) strlen(key_line))
2740 key_linepos = (int) strlen(key_line);
2749 stringlist_t resultbuf, dirbuf;
2752 // // store completion patterns (space separated) for command foo in con_completion_foo
2753 // set con_completion_foo "foodata/*.foodefault *.foo"
2756 // Note: patterns with slash are always treated as absolute
2757 // patterns; patterns without slash search in the innermost
2758 // directory the user specified. There is no way to "complete into"
2759 // a directory as of now, as directories seem to be unknown to the
2763 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2764 // set con_completion_playdemo "*.dem"
2765 // set con_completion_play "*.wav *.ogg"
2767 // TODO somehow add support for directories; these shall complete
2768 // to their name + an appended slash.
2770 stringlistinit(&resultbuf);
2771 stringlistinit(&dirbuf);
2772 while(COM_ParseToken_Simple(&patterns, false, false, true))
2775 if(strchr(com_token, '/'))
2777 search = FS_Search(com_token, true, true);
2781 const char *slash = strrchr(s, '/');
2784 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2785 strlcat(t, com_token, sizeof(t));
2786 search = FS_Search(t, true, true);
2789 search = FS_Search(com_token, true, true);
2793 for(i = 0; i < search->numfilenames; ++i)
2794 if(!strncmp(search->filenames[i], s, strlen(s)))
2795 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2796 stringlistappend(&resultbuf, search->filenames[i]);
2797 FS_FreeSearch(search);
2801 // In any case, add directory names
2804 const char *slash = strrchr(s, '/');
2807 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2808 strlcat(t, "*", sizeof(t));
2809 search = FS_Search(t, true, true);
2812 search = FS_Search("*", true, true);
2815 for(i = 0; i < search->numfilenames; ++i)
2816 if(!strncmp(search->filenames[i], s, strlen(s)))
2817 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2818 stringlistappend(&dirbuf, search->filenames[i]);
2819 FS_FreeSearch(search);
2823 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2826 unsigned int matchchars;
2827 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2829 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2832 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2834 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2838 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2839 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2840 for(i = 0; i < dirbuf.numstrings; ++i)
2842 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2844 for(i = 0; i < resultbuf.numstrings; ++i)
2846 Con_Printf("%s\n", resultbuf.strings[i]);
2848 matchchars = sizeof(t) - 1;
2849 if(resultbuf.numstrings > 0)
2851 p = resultbuf.strings[0];
2852 q = resultbuf.strings[resultbuf.numstrings - 1];
2853 for(; *p && *p == *q; ++p, ++q);
2854 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2856 if(dirbuf.numstrings > 0)
2858 p = dirbuf.strings[0];
2859 q = dirbuf.strings[dirbuf.numstrings - 1];
2860 for(; *p && *p == *q; ++p, ++q);
2861 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2863 // now p points to the first non-equal character, or to the end
2864 // of resultbuf.strings[0]. We want to append the characters
2865 // from resultbuf.strings[0] to (not including) p as these are
2866 // the unique prefix
2867 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2870 // first move the cursor
2871 key_linepos += (int)strlen(t) - (int)strlen(s);
2873 // and now do the actual work
2875 strlcat(key_line, t, MAX_INPUTLINE);
2876 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2878 // and fix the cursor
2879 if(key_linepos > (int) strlen(key_line))
2880 key_linepos = (int) strlen(key_line);
2882 stringlistfreecontents(&resultbuf);
2883 stringlistfreecontents(&dirbuf);
2885 return; // bail out, when we complete for a command that wants a file name
2890 // Count number of possible matches and print them
2891 c = Cmd_CompleteCountPossible(s);
2894 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2895 Cmd_CompleteCommandPrint(s);
2897 v = Cvar_CompleteCountPossible(s);
2900 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2901 Cvar_CompleteCvarPrint(s);
2903 a = Cmd_CompleteAliasCountPossible(s);
2906 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2907 Cmd_CompleteAliasPrint(s);
2909 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2912 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2913 Cmd_CompleteNicksPrint(n);
2916 if (!(c + v + a + n)) // No possible matches
2919 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2924 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2926 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2928 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2930 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2932 for (cmd_len = (int)strlen(s);;cmd_len++)
2935 for (i = 0; i < 3; i++)
2937 for (l = list[i];*l;l++)
2938 if ((*l)[cmd_len] != cmd[cmd_len])
2940 // all possible matches share this character, so we continue...
2943 // if all matches ended at the same position, stop
2944 // (this means there is only one match)
2950 // prevent a buffer overrun by limiting cmd_len according to remaining space
2951 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2955 memcpy(&key_line[key_linepos], cmd, cmd_len);
2956 key_linepos += cmd_len;
2957 // if there is only one match, add a space after it
2958 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2961 { // was a nick, might have an offset, and needs colors ;) --blub
2962 key_linepos = pos - Nicks_offset[0];
2963 cmd_len = strlen(Nicks_list[0]);
2964 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2966 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2967 key_linepos += cmd_len;
2968 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2969 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2971 key_line[key_linepos++] = ' ';
2975 // use strlcat to avoid a buffer overrun
2976 key_line[key_linepos] = 0;
2977 strlcat(key_line, s2, sizeof(key_line));
2979 // free the command, cvar, and alias lists
2980 for (i = 0; i < 4; i++)
2982 Mem_Free((void *)list[i]);