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 if(!(CON_LINES(i).mask & CON_MASK_CHAT))
594 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
603 static void Con_MessageMode_f (void)
605 key_dest = key_message;
606 chat_mode = 0; // "say"
609 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
610 chat_bufferlen = strlen(chat_buffer);
620 static void Con_MessageMode2_f (void)
622 key_dest = key_message;
623 chat_mode = 1; // "say_team"
626 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
627 chat_bufferlen = strlen(chat_buffer);
636 static void Con_CommandMode_f (void)
638 key_dest = key_message;
641 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
642 chat_bufferlen = strlen(chat_buffer);
644 chat_mode = -1; // command
652 void Con_CheckResize (void)
657 f = bound(1, con_textsize.value, 128);
658 if(f != con_textsize.value)
659 Cvar_SetValueQuick(&con_textsize, f);
660 width = (int)floor(vid_conwidth.value / con_textsize.value);
661 width = bound(1, width, con.textsize/4);
662 // FIXME uses con in a non abstracted way
664 if (width == con_linewidth)
667 con_linewidth = width;
669 for(i = 0; i < CON_LINES_COUNT; ++i)
670 CON_LINES(i).height = -1; // recalculate when next needed
676 //[515]: the simplest command ever
677 //LordHavoc: not so simple after I made it print usage...
678 static void Con_Maps_f (void)
682 Con_Printf("usage: maps [mapnameprefix]\n");
685 else if (Cmd_Argc() == 2)
686 GetMapList(Cmd_Argv(1), NULL, 0);
688 GetMapList("", NULL, 0);
691 static void Con_ConDump_f (void)
697 Con_Printf("usage: condump <filename>\n");
700 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
703 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
706 if (con_mutex) Thread_LockMutex(con_mutex);
707 for(i = 0; i < CON_LINES_COUNT; ++i)
709 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
710 FS_Write(file, "\n", 1);
712 if (con_mutex) Thread_UnlockMutex(con_mutex);
716 void Con_Clear_f (void)
718 if (con_mutex) Thread_LockMutex(con_mutex);
719 ConBuffer_Clear(&con);
720 if (con_mutex) Thread_UnlockMutex(con_mutex);
731 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
732 if (Thread_HasThreads())
733 con_mutex = Thread_CreateMutex();
735 // Allocate a log queue, this will be freed after configs are parsed
736 logq_size = MAX_INPUTLINE;
737 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
740 Cvar_RegisterVariable (&sys_colortranslation);
741 Cvar_RegisterVariable (&sys_specialcharactertranslation);
743 Cvar_RegisterVariable (&log_file);
744 Cvar_RegisterVariable (&log_dest_udp);
746 // support for the classic Quake option
747 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
748 if (COM_CheckParm ("-condebug") != 0)
749 Cvar_SetQuick (&log_file, "qconsole.log");
751 // register our cvars
752 Cvar_RegisterVariable (&con_chat);
753 Cvar_RegisterVariable (&con_chatpos);
754 Cvar_RegisterVariable (&con_chatrect_x);
755 Cvar_RegisterVariable (&con_chatrect_y);
756 Cvar_RegisterVariable (&con_chatrect);
757 Cvar_RegisterVariable (&con_chatsize);
758 Cvar_RegisterVariable (&con_chattime);
759 Cvar_RegisterVariable (&con_chatwidth);
760 Cvar_RegisterVariable (&con_notify);
761 Cvar_RegisterVariable (&con_notifyalign);
762 Cvar_RegisterVariable (&con_notifysize);
763 Cvar_RegisterVariable (&con_notifytime);
764 Cvar_RegisterVariable (&con_textsize);
765 Cvar_RegisterVariable (&con_chatsound);
768 Cvar_RegisterVariable (&con_nickcompletion);
769 Cvar_RegisterVariable (&con_nickcompletion_flags);
771 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
772 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
773 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
775 // register our commands
776 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
777 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
778 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
779 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
780 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
781 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
782 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
784 con_initialized = true;
785 Con_DPrint("Console initialized.\n");
788 void Con_Shutdown (void)
790 if (con_mutex) Thread_LockMutex(con_mutex);
791 ConBuffer_Shutdown(&con);
792 if (con_mutex) Thread_UnlockMutex(con_mutex);
793 if (con_mutex) Thread_DestroyMutex(con_mutex);con_mutex = NULL;
800 Handles cursor positioning, line wrapping, etc
801 All console printing must go through this in order to be displayed
802 If no console is visible, the notify window will pop up.
805 static void Con_PrintToHistory(const char *txt, int mask)
808 // \n goes to next line
809 // \r deletes current line and makes a new one
811 static int cr_pending = 0;
812 static char buf[CON_TEXTSIZE]; // con_mutex
813 static int bufpos = 0;
815 if(!con.text) // FIXME uses a non-abstracted property of con
822 ConBuffer_DeleteLastLine(&con);
830 ConBuffer_AddLine(&con, buf, bufpos, mask);
835 ConBuffer_AddLine(&con, buf, bufpos, mask);
839 buf[bufpos++] = *txt;
840 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
842 ConBuffer_AddLine(&con, buf, bufpos, mask);
850 /*! The translation table between the graphical font and plain ASCII --KB */
851 static char qfont_table[256] = {
852 '\0', '#', '#', '#', '#', '.', '#', '#',
853 '#', 9, 10, '#', ' ', 13, '.', '.',
854 '[', ']', '0', '1', '2', '3', '4', '5',
855 '6', '7', '8', '9', '.', '<', '=', '>',
856 ' ', '!', '"', '#', '$', '%', '&', '\'',
857 '(', ')', '*', '+', ',', '-', '.', '/',
858 '0', '1', '2', '3', '4', '5', '6', '7',
859 '8', '9', ':', ';', '<', '=', '>', '?',
860 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
861 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
862 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
863 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
864 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
865 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
866 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
867 'x', 'y', 'z', '{', '|', '}', '~', '<',
869 '<', '=', '>', '#', '#', '.', '#', '#',
870 '#', '#', ' ', '#', ' ', '>', '.', '.',
871 '[', ']', '0', '1', '2', '3', '4', '5',
872 '6', '7', '8', '9', '.', '<', '=', '>',
873 ' ', '!', '"', '#', '$', '%', '&', '\'',
874 '(', ')', '*', '+', ',', '-', '.', '/',
875 '0', '1', '2', '3', '4', '5', '6', '7',
876 '8', '9', ':', ';', '<', '=', '>', '?',
877 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
878 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
879 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
880 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
881 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
882 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
883 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
884 'x', 'y', 'z', '{', '|', '}', '~', '<'
887 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest, qboolean proquakeprotocol)
889 rcon_redirect_sock = sock;
890 rcon_redirect_dest = dest;
891 rcon_redirect_proquakeprotocol = proquakeprotocol;
892 if (rcon_redirect_proquakeprotocol)
894 // reserve space for the packet header
895 rcon_redirect_buffer[0] = 0;
896 rcon_redirect_buffer[1] = 0;
897 rcon_redirect_buffer[2] = 0;
898 rcon_redirect_buffer[3] = 0;
899 // this is a reply to a CCREQ_RCON
900 rcon_redirect_buffer[4] = (char)CCREP_RCON;
903 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
904 rcon_redirect_bufferpos = 5;
907 static void Con_Rcon_Redirect_Flush(void)
909 if(rcon_redirect_sock)
911 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
912 if (rcon_redirect_proquakeprotocol)
914 // update the length in the packet header
915 StoreBigLong((unsigned char *)rcon_redirect_buffer, NETFLAG_CTL | (rcon_redirect_bufferpos & NETFLAG_LENGTH_MASK));
917 NetConn_Write(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_bufferpos, rcon_redirect_dest);
919 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
920 rcon_redirect_bufferpos = 5;
921 rcon_redirect_proquakeprotocol = false;
924 void Con_Rcon_Redirect_End(void)
926 Con_Rcon_Redirect_Flush();
927 rcon_redirect_dest = NULL;
928 rcon_redirect_sock = NULL;
931 void Con_Rcon_Redirect_Abort(void)
933 rcon_redirect_dest = NULL;
934 rcon_redirect_sock = NULL;
942 /// Adds a character to the rcon buffer.
943 static void Con_Rcon_AddChar(int c)
945 if(log_dest_buffer_appending)
947 ++log_dest_buffer_appending;
949 // if this print is in response to an rcon command, add the character
950 // to the rcon redirect buffer
952 if (rcon_redirect_dest)
954 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
955 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
956 Con_Rcon_Redirect_Flush();
958 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
960 if(log_dest_buffer_pos == 0)
961 Log_DestBuffer_Init();
962 log_dest_buffer[log_dest_buffer_pos++] = c;
963 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
964 Log_DestBuffer_Flush_NoLock();
967 log_dest_buffer_pos = 0;
969 --log_dest_buffer_appending;
973 * Convert an RGB color to its nearest quake color.
974 * I'll cheat on this a bit by translating the colors to HSV first,
975 * S and V decide if it's black or white, otherwise, H will decide the
977 * @param _r Red (0-255)
978 * @param _g Green (0-255)
979 * @param _b Blue (0-255)
980 * @return A quake color character.
982 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
984 float r = ((float)_r)/255.0;
985 float g = ((float)_g)/255.0;
986 float b = ((float)_b)/255.0;
987 float min = min(r, min(g, b));
988 float max = max(r, max(g, b));
990 int h; ///< Hue angle [0,360]
991 float s; ///< Saturation [0,1]
992 float v = max; ///< In HSV v == max [0,1]
999 // Saturation threshold. We now say 0.2 is the minimum value for a color!
1002 // If the value is less than half, return a black color code.
1003 // Otherwise return a white one.
1009 // Let's get the hue angle to define some colors:
1013 h = (int)(60.0 * (g-b)/(max-min))%360;
1015 h = (int)(60.0 * (b-r)/(max-min) + 120);
1016 else // if(max == b) redundant check
1017 h = (int)(60.0 * (r-g)/(max-min) + 240);
1019 if(h < 36) // *red* to orange
1021 else if(h < 80) // orange over *yellow* to evilish-bright-green
1023 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1025 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1027 else if(h < 270) // darkish blue over *dark blue* to cool purple
1029 else if(h < 330) // cool purple over *purple* to ugly swiny red
1031 else // ugly red to red closes the circly
1040 extern cvar_t timestamps;
1041 extern cvar_t timeformat;
1042 extern qboolean sys_nostdout;
1043 void Con_MaskPrint(int additionalmask, const char *msg)
1045 static int mask = 0;
1046 static int index = 0;
1047 static char line[MAX_INPUTLINE];
1050 Thread_LockMutex(con_mutex);
1054 Con_Rcon_AddChar(*msg);
1055 // if this is the beginning of a new line, print timestamp
1058 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1060 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1061 line[index++] = STRING_COLOR_TAG;
1062 // assert( STRING_COLOR_DEFAULT < 10 )
1063 line[index++] = STRING_COLOR_DEFAULT + '0';
1064 // special color codes for chat messages must always come first
1065 // for Con_PrintToHistory to work properly
1066 if (*msg == 1 || *msg == 2 || *msg == 3)
1071 if (con_chatsound.value)
1073 if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
1075 if(msg[1] == '\r' && cl.foundtalk2wav)
1076 S_LocalSound ("sound/misc/talk2.wav");
1078 S_LocalSound ("sound/misc/talk.wav");
1082 if (msg[1] == '(' && cl.foundtalk2wav)
1083 S_LocalSound ("sound/misc/talk2.wav");
1085 S_LocalSound ("sound/misc/talk.wav");
1090 // Send to chatbox for say/tell (1) and messages (3)
1091 // 3 is just so that a message can be sent to the chatbox without a sound.
1092 if (*msg == 1 || *msg == 3)
1093 mask = CON_MASK_CHAT;
1095 line[index++] = STRING_COLOR_TAG;
1096 line[index++] = '3';
1098 Con_Rcon_AddChar(*msg);
1101 for (;*timestamp;index++, timestamp++)
1102 if (index < (int)sizeof(line) - 2)
1103 line[index] = *timestamp;
1105 mask |= additionalmask;
1107 // append the character
1108 line[index++] = *msg;
1109 // if this is a newline character, we have a complete line to print
1110 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1112 // terminate the line
1116 // send to scrollable buffer
1117 if (con_initialized && cls.state != ca_dedicated)
1119 Con_PrintToHistory(line, mask);
1121 // send to terminal or dedicated server window
1123 if (developer.integer || !(mask & CON_MASK_DEVELOPER))
1125 if(sys_specialcharactertranslation.integer)
1132 int ch = u8_getchar(p, &q);
1133 if(ch >= 0xE000 && ch <= 0xE0FF && ((unsigned char) qfont_table[ch - 0xE000]) >= 0x20)
1135 *p = qfont_table[ch - 0xE000];
1137 memmove(p+1, q, strlen(q)+1);
1145 if(sys_colortranslation.integer == 1) // ANSI
1147 static char printline[MAX_INPUTLINE * 4 + 3];
1148 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1149 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1154 for(in = line, out = printline; *in; ++in)
1158 case STRING_COLOR_TAG:
1159 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1161 char r = tolower(in[2]);
1162 char g = tolower(in[3]);
1163 char b = tolower(in[4]);
1164 // it's a hex digit already, so the else part needs no check --blub
1165 if(isdigit(r)) r -= '0';
1167 if(isdigit(g)) g -= '0';
1169 if(isdigit(b)) b -= '0';
1172 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1173 in += 3; // 3 only, the switch down there does the fourth
1180 case STRING_COLOR_TAG:
1182 *out++ = STRING_COLOR_TAG;
1188 if(lastcolor == 0) break; else lastcolor = 0;
1189 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1194 if(lastcolor == 1) break; else lastcolor = 1;
1195 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1200 if(lastcolor == 2) break; else lastcolor = 2;
1201 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1206 if(lastcolor == 3) break; else lastcolor = 3;
1207 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1212 if(lastcolor == 4) break; else lastcolor = 4;
1213 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1218 if(lastcolor == 5) break; else lastcolor = 5;
1219 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1224 if(lastcolor == 6) break; else lastcolor = 6;
1225 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1230 // bold normal color
1232 if(lastcolor == 8) break; else lastcolor = 8;
1233 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1236 *out++ = STRING_COLOR_TAG;
1243 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1260 Sys_PrintToTerminal(printline);
1262 else if(sys_colortranslation.integer == 2) // Quake
1264 Sys_PrintToTerminal(line);
1268 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1271 for(in = line, out = printline; *in; ++in)
1275 case STRING_COLOR_TAG:
1278 case STRING_COLOR_RGB_TAG_CHAR:
1279 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1284 *out++ = STRING_COLOR_TAG;
1285 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1288 case STRING_COLOR_TAG:
1290 *out++ = STRING_COLOR_TAG;
1305 *out++ = STRING_COLOR_TAG;
1315 Sys_PrintToTerminal(printline);
1318 // empty the line buffer
1325 Thread_UnlockMutex(con_mutex);
1333 void Con_MaskPrintf(int mask, const char *fmt, ...)
1336 char msg[MAX_INPUTLINE];
1338 va_start(argptr,fmt);
1339 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1342 Con_MaskPrint(mask, msg);
1350 void Con_Print(const char *msg)
1352 Con_MaskPrint(CON_MASK_PRINT, msg);
1360 void Con_Printf(const char *fmt, ...)
1363 char msg[MAX_INPUTLINE];
1365 va_start(argptr,fmt);
1366 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1369 Con_MaskPrint(CON_MASK_PRINT, msg);
1377 void Con_DPrint(const char *msg)
1379 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1382 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1390 void Con_DPrintf(const char *fmt, ...)
1393 char msg[MAX_INPUTLINE];
1395 if(developer.integer < 0) // at 0, we still add to the buffer but hide
1398 va_start(argptr,fmt);
1399 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1402 Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1407 ==============================================================================
1411 ==============================================================================
1418 The input line scrolls horizontally if typing goes beyond the right edge
1420 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1423 extern cvar_t r_font_disable_freetype;
1424 static void Con_DrawInput (void)
1428 char editlinecopy[MAX_INPUTLINE+1], *text;
1433 if (!key_consoleactive)
1434 return; // don't draw anything
1436 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1437 text = editlinecopy;
1439 // Advanced Console Editing by Radix radix@planetquake.com
1440 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1441 // use strlen of edit_line instead of key_linepos to allow editing
1442 // of early characters w/o erasing
1444 y = (int)strlen(text);
1446 // append enoug nul-bytes to cover the utf8-versions of the cursor too
1447 for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1450 // add the cursor frame
1451 if (r_font_disable_freetype.integer)
1453 // this code is freetype incompatible!
1454 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1456 if (!utf8_enable.integer)
1457 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1458 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1460 int ofs = u8_bytelen(text + key_linepos, 1);
1464 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1468 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1469 memcpy(text + key_linepos, curbuf, len);
1472 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1476 // text[key_linepos + 1] = 0;
1478 len_out = key_linepos;
1480 xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1481 x = vid_conwidth.value * 0.95 - xo; // scroll
1486 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 );
1488 // add a cursor on top of this (when using freetype)
1489 if (!r_font_disable_freetype.integer)
1491 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1493 if (!utf8_enable.integer)
1495 text[0] = 11 + 130 * key_insert; // either solid or triangle facing right
1503 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len, charbuf16);
1504 memcpy(text, curbuf, len);
1507 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);
1512 // key_line[key_linepos] = 0;
1518 float alignment; // 0 = left, 0.5 = center, 1 = right
1524 const char *continuationString;
1527 int colorindex; // init to -1
1531 static float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1533 con_text_info_t *ti = (con_text_info_t *) passthrough;
1536 ti->colorindex = -1;
1537 return ti->fontsize * ti->font->maxwidth;
1540 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1541 else if(maxWidth == -1)
1542 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1545 Sys_PrintfToTerminal("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1546 // Note: this is NOT a Con_Printf, as it could print recursively
1551 static int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1557 (void) isContinuation;
1561 static int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1563 con_text_info_t *ti = (con_text_info_t *) passthrough;
1565 if(ti->y < ti->ymin - 0.001)
1567 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1571 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1572 if(isContinuation && *ti->continuationString)
1573 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);
1575 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);
1578 ti->y += ti->fontsize;
1582 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)
1586 int maxlines = (int) floor(height / fontsize + 0.01f);
1589 int continuationWidth = 0;
1591 double t = cl.time; // saved so it won't change
1594 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1595 ti.fontsize = fontsize;
1596 ti.alignment = alignment_x;
1599 ti.ymax = y + height;
1600 ti.continuationString = continuationString;
1603 Con_WordWidthFunc(&ti, NULL, &l, -1);
1604 l = strlen(continuationString);
1605 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1607 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1608 startidx = CON_LINES_COUNT;
1609 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1611 con_lineinfo_t *l = &CON_LINES(i);
1614 if((l->mask & mask_must) != mask_must)
1616 if(l->mask & mask_mustnot)
1618 if(maxage && (l->addtime < t - maxage))
1622 // Calculate its actual height...
1623 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1624 if(lines + mylines >= maxlines)
1626 nskip = lines + mylines - maxlines;
1635 // then center according to the calculated amount of lines...
1637 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1639 // then actually draw
1640 for(i = startidx; i < CON_LINES_COUNT; ++i)
1642 con_lineinfo_t *l = &CON_LINES(i);
1644 if((l->mask & mask_must) != mask_must)
1646 if(l->mask & mask_mustnot)
1648 if(maxage && (l->addtime < t - maxage))
1651 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1661 Draws the last few lines of output transparently over the game top
1664 void Con_DrawNotify (void)
1667 float chatstart, notifystart, inputsize, height;
1669 char temptext[MAX_INPUTLINE];
1673 if (con_mutex) Thread_LockMutex(con_mutex);
1674 ConBuffer_FixTimes(&con);
1676 numChatlines = con_chat.integer;
1678 chatpos = con_chatpos.integer;
1680 if (con_notify.integer < 0)
1681 Cvar_SetValueQuick(&con_notify, 0);
1682 if (gamemode == GAME_TRANSFUSION)
1683 v = 8; // vertical offset
1687 // GAME_NEXUIZ: center, otherwise left justify
1688 align = con_notifyalign.value;
1689 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1691 if(gamemode == GAME_NEXUIZ)
1695 if(numChatlines || !con_chatrect.integer)
1699 // first chat, input line, then notify
1701 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1703 else if(chatpos > 0)
1705 // first notify, then (chatpos-1) empty lines, then chat, then input
1707 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1709 else // if(chatpos < 0)
1711 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1713 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1718 // just notify and input
1720 chatstart = 0; // shut off gcc warning
1723 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, "");
1725 if(con_chatrect.integer)
1727 x = con_chatrect_x.value * vid_conwidth.value;
1728 v = con_chatrect_y.value * vid_conheight.value;
1733 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1736 height = numChatlines * con_chatsize.value;
1740 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
1743 if (key_dest == key_message)
1745 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1746 int colorindex = -1;
1749 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL, charbuf16);
1751 // LordHavoc: speedup, and other improvements
1753 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1755 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1757 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1760 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1761 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1763 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1765 if (con_mutex) Thread_UnlockMutex(con_mutex);
1772 Returns the height of a given console line; calculates it if necessary.
1775 static int Con_LineHeight(int lineno)
1777 con_lineinfo_t *li = &CON_LINES(lineno);
1778 if(li->height == -1)
1780 float width = vid_conwidth.value;
1782 con_lineinfo_t *li = &CON_LINES(lineno);
1783 ti.fontsize = con_textsize.value;
1784 ti.font = FONT_CONSOLE;
1785 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1794 Draws a line of the console; returns its height in lines.
1795 If alpha is 0, the line is not drawn, but still wrapped and its height
1799 static int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1801 float width = vid_conwidth.value;
1803 con_lineinfo_t *li = &CON_LINES(lineno);
1805 if((li->mask & mask_must) != mask_must)
1807 if((li->mask & mask_mustnot) != 0)
1810 ti.continuationString = "";
1812 ti.fontsize = con_textsize.value;
1813 ti.font = FONT_CONSOLE;
1815 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1820 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1827 Calculates the last visible line index and how much to show of it based on
1831 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1836 if(con_backscroll < 0)
1841 // now count until we saw con_backscroll actual lines
1842 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1843 if((CON_LINES(i).mask & mask_must) == mask_must)
1844 if((CON_LINES(i).mask & mask_mustnot) == 0)
1846 int h = Con_LineHeight(i);
1848 // line is the last visible line?
1850 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1852 *limitlast = lines_seen + h - con_backscroll;
1859 // if we get here, no line was on screen - scroll so that one line is
1861 con_backscroll = lines_seen - 1;
1869 Draws the console with the solid background
1870 The typing input line at the bottom should only be drawn if typing is allowed
1873 void Con_DrawConsole (int lines)
1875 float alpha, alpha0;
1878 int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1879 cachepic_t *conbackpic;
1884 if (con_mutex) Thread_LockMutex(con_mutex);
1886 if (con_backscroll < 0)
1889 con_vislines = lines;
1891 r_draw2d_force = true;
1893 // draw the background
1894 alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1895 if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1897 sx = scr_conscroll_x.value;
1898 sy = scr_conscroll_y.value;
1899 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1900 sx *= realtime; sy *= realtime;
1901 sx -= floor(sx); sy -= floor(sy);
1902 if (conbackpic && conbackpic->tex != r_texture_notexture)
1903 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1904 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1905 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1906 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1907 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1910 DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1912 if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1914 sx = scr_conscroll2_x.value;
1915 sy = scr_conscroll2_y.value;
1916 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1917 sx *= realtime; sy *= realtime;
1918 sx -= floor(sx); sy -= floor(sy);
1919 if(conbackpic && conbackpic->tex != r_texture_notexture)
1920 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1921 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1922 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1923 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1924 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1927 if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1929 sx = scr_conscroll3_x.value;
1930 sy = scr_conscroll3_y.value;
1931 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1932 sx *= realtime; sy *= realtime;
1933 sx -= floor(sx); sy -= floor(sy);
1934 if(conbackpic && conbackpic->tex != r_texture_notexture)
1935 DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1936 0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1937 1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1938 0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1939 1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1942 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);
1948 int count = CON_LINES_COUNT;
1949 float ymax = con_vislines - 2 * con_textsize.value;
1950 float y = ymax + con_textsize.value * con_backscroll;
1951 for (i = 0;i < count && y >= 0;i++)
1952 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1953 // fix any excessive scrollback for the next frame
1954 if (i >= count && y >= 0)
1956 con_backscroll -= (int)(y / con_textsize.value);
1957 if (con_backscroll < 0)
1962 if(CON_LINES_COUNT > 0)
1964 int i, last, limitlast;
1966 float ymax = con_vislines - 2 * con_textsize.value;
1967 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1968 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1969 y = ymax - con_textsize.value;
1972 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1977 y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1979 break; // top of console buffer
1981 break; // top of console window
1988 // draw the input prompt, user text, and cursor if desired
1991 r_draw2d_force = false;
1992 if (con_mutex) Thread_UnlockMutex(con_mutex);
1999 Prints not only map filename, but also
2000 its format (q1/q2/q3/hl) and even its message
2002 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
2003 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
2004 //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
2005 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
2006 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
2010 int i, k, max, p, o, min;
2013 unsigned char buf[1024];
2015 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
2016 t = FS_Search(message, 1, true);
2019 if (t->numfilenames > 1)
2020 Con_Printf("^1 %i maps found :\n", t->numfilenames);
2021 len = (unsigned char *)Z_Malloc(t->numfilenames);
2023 for(max=i=0;i<t->numfilenames;i++)
2025 k = (int)strlen(t->filenames[i]);
2035 for(i=0;i<t->numfilenames;i++)
2037 int lumpofs = 0, lumplen = 0;
2038 char *entities = NULL;
2039 const char *data = NULL;
2041 char entfilename[MAX_QPATH];
2044 strlcpy(message, "^1ERROR: open failed^7", sizeof(message));
2046 f = FS_OpenVirtualFile(t->filenames[i], true);
2049 strlcpy(message, "^1ERROR: not a known map format^7", sizeof(message));
2050 memset(buf, 0, 1024);
2051 FS_Read(f, buf, 1024);
2052 if (!memcmp(buf, "IBSP", 4))
2054 p = LittleLong(((int *)buf)[1]);
2055 if (p == Q3BSPVERSION)
2057 q3dheader_t *header = (q3dheader_t *)buf;
2058 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2059 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2060 dpsnprintf(desc, sizeof(desc), "Q3BSP%i", p);
2062 else if (p == Q2BSPVERSION)
2064 q2dheader_t *header = (q2dheader_t *)buf;
2065 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2066 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2067 dpsnprintf(desc, sizeof(desc), "Q2BSP%i", p);
2070 dpsnprintf(desc, sizeof(desc), "IBSP%i", p);
2072 else if (BuffLittleLong(buf) == BSPVERSION)
2074 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2075 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2076 dpsnprintf(desc, sizeof(desc), "BSP29");
2078 else if (BuffLittleLong(buf) == 30)
2080 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2081 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2082 dpsnprintf(desc, sizeof(desc), "BSPHL");
2084 else if (!memcmp(buf, "BSP2", 4))
2086 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2087 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2088 dpsnprintf(desc, sizeof(desc), "BSP2");
2090 else if (!memcmp(buf, "2PSB", 4))
2092 lumpofs = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES);
2093 lumplen = BuffLittleLong(buf + 4 + 8 * LUMP_ENTITIES + 4);
2094 dpsnprintf(desc, sizeof(desc), "BSP2RMQe");
2098 dpsnprintf(desc, sizeof(desc), "unknown%i", BuffLittleLong(buf));
2100 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2101 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2102 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2103 if (!entities && lumplen >= 10)
2105 FS_Seek(f, lumpofs, SEEK_SET);
2106 entities = (char *)Z_Malloc(lumplen + 1);
2107 FS_Read(f, entities, lumplen);
2111 // if there are entities to parse, a missing message key just
2112 // means there is no title, so clear the message string now
2118 if (!COM_ParseToken_Simple(&data, false, false, true))
2120 if (com_token[0] == '{')
2122 if (com_token[0] == '}')
2124 // skip leading whitespace
2125 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2126 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2127 keyname[l] = com_token[k+l];
2129 if (!COM_ParseToken_Simple(&data, false, false, true))
2131 if (developer_extra.integer)
2132 Con_DPrintf("key: %s %s\n", keyname, com_token);
2133 if (!strcmp(keyname, "message"))
2135 // get the message contents
2136 strlcpy(message, com_token, sizeof(message));
2146 *(t->filenames[i]+len[i]+5) = 0;
2147 Con_Printf("%16s (%-8s) %s\n", t->filenames[i]+5, desc, message);
2152 k = *(t->filenames[0]+5+p);
2155 for(i=1;i<t->numfilenames;i++)
2156 if(*(t->filenames[i]+5+p) != k)
2160 if(p > o && completedname && completednamebufferlength > 0)
2162 memset(completedname, 0, completednamebufferlength);
2163 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2173 New function for tab-completion system
2174 Added by EvilTypeGuy
2175 MEGA Thanks to Taniwha
2178 void Con_DisplayList(const char **list)
2180 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2181 const char **walk = list;
2184 len = (int)strlen(*walk);
2192 len = (int)strlen(*list);
2193 if (pos + maxlen >= width) {
2199 for (i = 0; i < (maxlen - len); i++)
2211 SanitizeString strips color tags from the string in
2212 and writes the result on string out
2214 static void SanitizeString(char *in, char *out)
2218 if(*in == STRING_COLOR_TAG)
2223 out[0] = STRING_COLOR_TAG;
2227 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2234 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2237 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2239 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2246 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2251 else if (*in != STRING_COLOR_TAG)
2254 *out = qfont_table[*(unsigned char*)in];
2261 // Now it becomes TRICKY :D --blub
2262 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2263 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2264 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2265 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
2266 static int Nicks_matchpos;
2268 // co against <<:BLASTER:>> is true!?
2269 static int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2273 if(tolower(*a) == tolower(*b))
2287 return (*a < *b) ? -1 : 1;
2291 return (*a < *b) ? -1 : 1;
2295 static int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2298 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2300 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2301 return Nicks_strncasecmp_nospaces(a, b, a_len);
2302 return strncasecmp(a, b, a_len);
2305 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2307 // ignore non alphanumerics of B
2308 // if A contains a non-alphanumeric, B must contain it as well though!
2311 qboolean alnum_a, alnum_b;
2313 if(tolower(*a) == tolower(*b))
2315 if(*a == 0) // end of both strings, they're equal
2322 // not equal, end of one string?
2327 // ignore non alphanumerics
2328 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2329 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2330 if(!alnum_a) // b must contain this
2331 return (*a < *b) ? -1 : 1;
2334 // otherwise, both are alnum, they're just not equal, return the appropriate number
2336 return (*a < *b) ? -1 : 1;
2342 /* Nicks_CompleteCountPossible
2344 Count the number of possible nicks to complete
2346 static int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2354 if(!con_nickcompletion.integer)
2357 // changed that to 1
2358 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2361 for(i = 0; i < cl.maxclients; ++i)
2364 if(!cl.scores[p].name[0])
2367 SanitizeString(cl.scores[p].name, name);
2368 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2374 spos = pos - 1; // no need for a minimum of characters :)
2378 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2380 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2381 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2387 if(isCon && spos == 0)
2389 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2395 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2396 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2398 // the sanitized list
2399 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2402 Nicks_matchpos = match;
2405 Nicks_offset[count] = s - (&line[match]);
2406 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2413 static void Cmd_CompleteNicksPrint(int count)
2416 for(i = 0; i < count; ++i)
2417 Con_Printf("%s\n", Nicks_list[i]);
2420 static void Nicks_CutMatchesNormal(int count)
2422 // cut match 0 down to the longest possible completion
2425 c = strlen(Nicks_sanlist[0]) - 1;
2426 for(i = 1; i < count; ++i)
2428 l = strlen(Nicks_sanlist[i]) - 1;
2432 for(l = 0; l <= c; ++l)
2433 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2439 Nicks_sanlist[0][c+1] = 0;
2440 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2443 static unsigned int Nicks_strcleanlen(const char *s)
2448 if( (*s >= 'a' && *s <= 'z') ||
2449 (*s >= 'A' && *s <= 'Z') ||
2450 (*s >= '0' && *s <= '9') ||
2458 static void Nicks_CutMatchesAlphaNumeric(int count)
2460 // cut match 0 down to the longest possible completion
2463 char tempstr[sizeof(Nicks_sanlist[0])];
2465 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2467 c = strlen(Nicks_sanlist[0]);
2468 for(i = 0, l = 0; i < (int)c; ++i)
2470 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2471 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2472 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2474 tempstr[l++] = Nicks_sanlist[0][i];
2479 for(i = 1; i < count; ++i)
2482 b = Nicks_sanlist[i];
2492 if(tolower(*a) == tolower(*b))
2498 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2500 // b is alnum, so cut
2507 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2508 Nicks_CutMatchesNormal(count);
2509 //if(!Nicks_sanlist[0][0])
2510 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2512 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2513 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2517 static void Nicks_CutMatchesNoSpaces(int count)
2519 // cut match 0 down to the longest possible completion
2522 char tempstr[sizeof(Nicks_sanlist[0])];
2525 c = strlen(Nicks_sanlist[0]);
2526 for(i = 0, l = 0; i < (int)c; ++i)
2528 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2530 tempstr[l++] = Nicks_sanlist[0][i];
2535 for(i = 1; i < count; ++i)
2538 b = Nicks_sanlist[i];
2548 if(tolower(*a) == tolower(*b))
2562 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2563 Nicks_CutMatchesNormal(count);
2564 //if(!Nicks_sanlist[0][0])
2565 //Con_Printf("TS: %s\n", tempstr);
2566 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2568 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2569 strlcpy(Nicks_sanlist[0], tempstr, sizeof(Nicks_sanlist[0]));
2573 static void Nicks_CutMatches(int count)
2575 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2576 Nicks_CutMatchesAlphaNumeric(count);
2577 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2578 Nicks_CutMatchesNoSpaces(count);
2580 Nicks_CutMatchesNormal(count);
2583 static const char **Nicks_CompleteBuildList(int count)
2587 // the list is freed by Con_CompleteCommandLine, so create a char**
2588 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2590 for(; bpos < count; ++bpos)
2591 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2593 Nicks_CutMatches(count);
2601 Restores the previous used color, after the autocompleted name.
2603 static int Nicks_AddLastColor(char *buffer, int pos)
2605 qboolean quote_added = false;
2607 int color = STRING_COLOR_DEFAULT + '0';
2608 char r = 0, g = 0, b = 0;
2610 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2612 // we'll have to add a quote :)
2613 buffer[pos++] = '\"';
2617 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2619 // add color when no quote was added, or when flags &4?
2621 for(match = Nicks_matchpos-1; match >= 0; --match)
2623 if(buffer[match] == STRING_COLOR_TAG)
2625 if( isdigit(buffer[match+1]) )
2627 color = buffer[match+1];
2630 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2632 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2634 r = buffer[match+2];
2635 g = buffer[match+3];
2636 b = buffer[match+4];
2645 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2647 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2648 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2651 buffer[pos++] = STRING_COLOR_TAG;
2654 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2660 buffer[pos++] = color;
2665 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2668 /*if(!con_nickcompletion.integer)
2669 return; is tested in Nicks_CompletionCountPossible */
2670 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2676 msg = Nicks_list[0];
2677 len = min(size - Nicks_matchpos - 3, strlen(msg));
2678 memcpy(&buffer[Nicks_matchpos], msg, len);
2679 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2680 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2681 buffer[len++] = ' ';
2688 Con_Printf("\n%i possible nicks:\n", n);
2689 Cmd_CompleteNicksPrint(n);
2691 Nicks_CutMatches(n);
2693 msg = Nicks_sanlist[0];
2694 len = min(size - Nicks_matchpos, strlen(msg));
2695 memcpy(&buffer[Nicks_matchpos], msg, len);
2696 buffer[Nicks_matchpos + len] = 0;
2698 return Nicks_matchpos + len;
2705 Con_CompleteCommandLine
2707 New function for tab-completion system
2708 Added by EvilTypeGuy
2709 Thanks to Fett erich@heintz.com
2711 Enhanced to tab-complete map names by [515]
2714 void Con_CompleteCommandLine (void)
2716 const char *cmd = "";
2718 const char **list[4] = {0, 0, 0, 0};
2721 int c, v, a, i, cmd_len, pos, k;
2722 int n; // nicks --blub
2723 const char *space, *patterns;
2726 //find what we want to complete
2731 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2737 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2738 key_line[key_linepos] = 0; //hide them
2740 space = strchr(key_line + 1, ' ');
2741 if(space && pos == (space - key_line) + 1)
2743 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2745 patterns = Cvar_VariableString(va(vabuf, sizeof(vabuf), "con_completion_%s", command)); // TODO maybe use a better place for this?
2746 if(patterns && !*patterns)
2747 patterns = NULL; // get rid of the empty string
2749 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2753 if (GetMapList(s, t, sizeof(t)))
2755 // first move the cursor
2756 key_linepos += (int)strlen(t) - (int)strlen(s);
2758 // and now do the actual work
2760 strlcat(key_line, t, MAX_INPUTLINE);
2761 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2763 // and fix the cursor
2764 if(key_linepos > (int) strlen(key_line))
2765 key_linepos = (int) strlen(key_line);
2774 stringlist_t resultbuf, dirbuf;
2777 // // store completion patterns (space separated) for command foo in con_completion_foo
2778 // set con_completion_foo "foodata/*.foodefault *.foo"
2781 // Note: patterns with slash are always treated as absolute
2782 // patterns; patterns without slash search in the innermost
2783 // directory the user specified. There is no way to "complete into"
2784 // a directory as of now, as directories seem to be unknown to the
2788 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2789 // set con_completion_playdemo "*.dem"
2790 // set con_completion_play "*.wav *.ogg"
2792 // TODO somehow add support for directories; these shall complete
2793 // to their name + an appended slash.
2795 stringlistinit(&resultbuf);
2796 stringlistinit(&dirbuf);
2797 while(COM_ParseToken_Simple(&patterns, false, false, true))
2800 if(strchr(com_token, '/'))
2802 search = FS_Search(com_token, true, true);
2806 const char *slash = strrchr(s, '/');
2809 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2810 strlcat(t, com_token, sizeof(t));
2811 search = FS_Search(t, true, true);
2814 search = FS_Search(com_token, true, true);
2818 for(i = 0; i < search->numfilenames; ++i)
2819 if(!strncmp(search->filenames[i], s, strlen(s)))
2820 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2821 stringlistappend(&resultbuf, search->filenames[i]);
2822 FS_FreeSearch(search);
2826 // In any case, add directory names
2829 const char *slash = strrchr(s, '/');
2832 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2833 strlcat(t, "*", sizeof(t));
2834 search = FS_Search(t, true, true);
2837 search = FS_Search("*", true, true);
2840 for(i = 0; i < search->numfilenames; ++i)
2841 if(!strncmp(search->filenames[i], s, strlen(s)))
2842 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2843 stringlistappend(&dirbuf, search->filenames[i]);
2844 FS_FreeSearch(search);
2848 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2851 unsigned int matchchars;
2852 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2854 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2857 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2859 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2863 stringlistsort(&resultbuf, true); // dirbuf is already sorted
2864 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2865 for(i = 0; i < dirbuf.numstrings; ++i)
2867 Con_Printf("^4%s^7/\n", dirbuf.strings[i]);
2869 for(i = 0; i < resultbuf.numstrings; ++i)
2871 Con_Printf("%s\n", resultbuf.strings[i]);
2873 matchchars = sizeof(t) - 1;
2874 if(resultbuf.numstrings > 0)
2876 p = resultbuf.strings[0];
2877 q = resultbuf.strings[resultbuf.numstrings - 1];
2878 for(; *p && *p == *q; ++p, ++q);
2879 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2881 if(dirbuf.numstrings > 0)
2883 p = dirbuf.strings[0];
2884 q = dirbuf.strings[dirbuf.numstrings - 1];
2885 for(; *p && *p == *q; ++p, ++q);
2886 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2888 // now p points to the first non-equal character, or to the end
2889 // of resultbuf.strings[0]. We want to append the characters
2890 // from resultbuf.strings[0] to (not including) p as these are
2891 // the unique prefix
2892 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2895 // first move the cursor
2896 key_linepos += (int)strlen(t) - (int)strlen(s);
2898 // and now do the actual work
2900 strlcat(key_line, t, MAX_INPUTLINE);
2901 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2903 // and fix the cursor
2904 if(key_linepos > (int) strlen(key_line))
2905 key_linepos = (int) strlen(key_line);
2907 stringlistfreecontents(&resultbuf);
2908 stringlistfreecontents(&dirbuf);
2910 return; // bail out, when we complete for a command that wants a file name
2915 // Count number of possible matches and print them
2916 c = Cmd_CompleteCountPossible(s);
2919 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2920 Cmd_CompleteCommandPrint(s);
2922 v = Cvar_CompleteCountPossible(s);
2925 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2926 Cvar_CompleteCvarPrint(s);
2928 a = Cmd_CompleteAliasCountPossible(s);
2931 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2932 Cmd_CompleteAliasPrint(s);
2934 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2937 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2938 Cmd_CompleteNicksPrint(n);
2941 if (!(c + v + a + n)) // No possible matches
2944 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2949 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2951 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2953 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2955 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2957 for (cmd_len = (int)strlen(s);;cmd_len++)
2960 for (i = 0; i < 3; i++)
2962 for (l = list[i];*l;l++)
2963 if ((*l)[cmd_len] != cmd[cmd_len])
2965 // all possible matches share this character, so we continue...
2968 // if all matches ended at the same position, stop
2969 // (this means there is only one match)
2975 // prevent a buffer overrun by limiting cmd_len according to remaining space
2976 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2980 memcpy(&key_line[key_linepos], cmd, cmd_len);
2981 key_linepos += cmd_len;
2982 // if there is only one match, add a space after it
2983 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2986 { // was a nick, might have an offset, and needs colors ;) --blub
2987 key_linepos = pos - Nicks_offset[0];
2988 cmd_len = strlen(Nicks_list[0]);
2989 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2991 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2992 key_linepos += cmd_len;
2993 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2994 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2996 key_line[key_linepos++] = ' ';
3000 // use strlcat to avoid a buffer overrun
3001 key_line[key_linepos] = 0;
3002 strlcat(key_line, s2, sizeof(key_line));
3004 // free the command, cvar, and alias lists
3005 for (i = 0; i < 4; i++)
3007 Mem_Free((void *)list[i]);