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.
24 #if !defined(WIN32) || defined(__MINGW32__)
33 float con_cursorspeed = 4;
35 // lines up from bottom to display
40 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
41 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
42 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
44 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
45 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
46 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
48 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
49 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
50 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)"};
51 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
52 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
53 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
54 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
55 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
58 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)"};
60 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)"};
62 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)"};
66 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
67 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
68 "0: add nothing after completion. "
69 "1: add the last color after completion. "
70 "2: add a quote when starting a quote instead of the color. "
71 "4: will replace 1, will force color, even after a quote. "
72 "8: ignore non-alphanumerics. "
73 "16: ignore spaces. "};
74 #define NICKS_ADD_COLOR 1
75 #define NICKS_ADD_QUOTE 2
76 #define NICKS_FORCE_COLOR 4
77 #define NICKS_ALPHANUMERICS_ONLY 8
78 #define NICKS_NO_SPACES 16
80 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
81 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
82 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
87 qboolean con_initialized;
89 // used for server replies to rcon command
90 lhnetsocket_t *rcon_redirect_sock = NULL;
91 lhnetaddress_t *rcon_redirect_dest = NULL;
92 int rcon_redirect_bufferpos = 0;
93 char rcon_redirect_buffer[1400];
95 // generic functions for console buffers
97 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
100 buf->textsize = textsize;
101 buf->text = (char *) Mem_Alloc(mempool, textsize);
102 buf->maxlines = maxlines;
103 buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
104 buf->lines_first = 0;
105 buf->lines_count = 0;
113 void ConBuffer_Clear (conbuffer_t *buf)
115 buf->lines_count = 0;
123 void ConBuffer_Shutdown(conbuffer_t *buf)
127 Mem_Free(buf->lines);
136 Notifies the console code about the current time
137 (and shifts back times of other entries when the time
141 void ConBuffer_FixTimes(conbuffer_t *buf)
144 if(buf->lines_count >= 1)
146 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
149 for(i = 0; i < buf->lines_count; ++i)
150 CONBUFFER_LINES(buf, i).addtime += diff;
159 Deletes the first line from the console history.
162 void ConBuffer_DeleteLine(conbuffer_t *buf)
164 if(buf->lines_count == 0)
167 buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
172 ConBuffer_DeleteLastLine
174 Deletes the last line from the console history.
177 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
179 if(buf->lines_count == 0)
188 Checks if there is space for a line of the given length, and if yes, returns a
189 pointer to the start of such a space, and NULL otherwise.
192 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
194 if(len > buf->textsize)
196 if(buf->lines_count == 0)
200 char *firstline_start = buf->lines[buf->lines_first].start;
201 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
202 // the buffer is cyclic, so we first have two cases...
203 if(firstline_start < lastline_onepastend) // buffer is contiguous
206 if(len <= buf->text + buf->textsize - lastline_onepastend)
207 return lastline_onepastend;
209 else if(len <= firstline_start - buf->text)
214 else // buffer has a contiguous hole
216 if(len <= firstline_start - lastline_onepastend)
217 return lastline_onepastend;
228 Appends a given string as a new line to the console.
231 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
236 // developer_memory 1 during shutdown prints while conbuffer_t is being freed
240 ConBuffer_FixTimes(buf);
242 if(len >= buf->textsize)
245 // only display end of line.
246 line += len - buf->textsize + 1;
247 len = buf->textsize - 1;
249 while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
250 ConBuffer_DeleteLine(buf);
251 memcpy(putpos, line, len);
255 //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
257 p = &CONBUFFER_LINES_LAST(buf);
260 p->addtime = cl.time;
262 p->height = -1; // calculate when needed
265 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
269 start = buf->lines_count;
270 for(i = start - 1; i >= 0; --i)
272 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
274 if((l->mask & mask_must) != mask_must)
276 if(l->mask & mask_mustnot)
285 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
288 for(i = start + 1; i < buf->lines_count; ++i)
290 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
292 if((l->mask & mask_must) != mask_must)
294 if(l->mask & mask_mustnot)
303 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
305 static char copybuf[MAX_INPUTLINE];
306 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
307 size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
308 strlcpy(copybuf, l->start, sz);
313 ==============================================================================
317 ==============================================================================
322 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
323 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"};
324 char log_dest_buffer[1400]; // UDP packet
325 size_t log_dest_buffer_pos;
326 unsigned int log_dest_buffer_appending;
327 char crt_log_file [MAX_OSPATH] = "";
328 qfile_t* logfile = NULL;
330 unsigned char* logqueue = NULL;
332 size_t logq_size = 0;
334 void Log_ConPrint (const char *msg);
341 static void Log_DestBuffer_Init(void)
343 memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
344 log_dest_buffer_pos = 5;
352 void Log_DestBuffer_Flush(void)
354 lhnetaddress_t log_dest_addr;
355 lhnetsocket_t *log_dest_socket;
356 const char *s = log_dest_udp.string;
357 qboolean have_opened_temp_sockets = false;
358 if(s) if(log_dest_buffer_pos > 5)
360 ++log_dest_buffer_appending;
361 log_dest_buffer[log_dest_buffer_pos++] = 0;
363 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
365 have_opened_temp_sockets = true;
366 NetConn_OpenServerPorts(true);
369 while(COM_ParseToken_Console(&s))
370 if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
372 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
374 log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
376 NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
379 if(have_opened_temp_sockets)
380 NetConn_CloseServerPorts();
381 --log_dest_buffer_appending;
383 log_dest_buffer_pos = 0;
391 const char* Log_Timestamp (const char *desc)
393 static char timestamp [128];
400 char timestring [64];
402 // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
405 localtime_s (&crt_tm, &crt_time);
406 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
408 crt_tm = localtime (&crt_time);
409 strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
413 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
415 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
428 if (logfile != NULL || log_file.string[0] == '\0')
431 logfile = FS_OpenRealFile(log_file.string, "a", false);
434 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
435 FS_Print (logfile, Log_Timestamp ("Log started"));
445 void Log_Close (void)
450 FS_Print (logfile, Log_Timestamp ("Log stopped"));
451 FS_Print (logfile, "\n");
455 crt_log_file[0] = '\0';
464 void Log_Start (void)
470 // Dump the contents of the log queue into the log file and free it
471 if (logqueue != NULL)
473 unsigned char *temp = logqueue;
478 FS_Write (logfile, temp, logq_ind);
479 if(*log_dest_udp.string)
481 for(pos = 0; pos < logq_ind; )
483 if(log_dest_buffer_pos == 0)
484 Log_DestBuffer_Init();
485 n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
486 memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
487 log_dest_buffer_pos += n;
488 Log_DestBuffer_Flush();
505 void Log_ConPrint (const char *msg)
507 static qboolean inprogress = false;
509 // don't allow feedback loops with memory error reports
514 // Until the host is completely initialized, we maintain a log queue
515 // to store the messages, since the log can't be started before
516 if (logqueue != NULL)
518 size_t remain = logq_size - logq_ind;
519 size_t len = strlen (msg);
521 // If we need to enlarge the log queue
524 size_t factor = ((logq_ind + len) / logq_size) + 1;
525 unsigned char* newqueue;
528 newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
529 memcpy (newqueue, logqueue, logq_ind);
532 remain = logq_size - logq_ind;
534 memcpy (&logqueue[logq_ind], msg, len);
541 // Check if log_file has changed
542 if (strcmp (crt_log_file, log_file.string) != 0)
548 // If a log file is available
550 FS_Print (logfile, msg);
561 void Log_Printf (const char *logfilename, const char *fmt, ...)
565 file = FS_OpenRealFile(logfilename, "a", true);
570 va_start (argptr, fmt);
571 FS_VPrintf (file, fmt, argptr);
580 ==============================================================================
584 ==============================================================================
592 void Con_ToggleConsole_f (void)
594 // toggle the 'user wants console' bit
595 key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
604 void Con_ClearNotify (void)
607 for(i = 0; i < CON_LINES_COUNT; ++i)
608 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
611 void Con_MessageMode_SetCursor(size_t pos)
613 chat_bufferlen = pos;
614 //chat_bufferlen = strlen(chat_buffer);
622 void Con_MessageMode_f (void)
624 key_dest = key_message;
625 chat_mode = 0; // "say"
629 UIM_EnterBuffer(chat_buffer, sizeof(chat_buffer), 0, &Con_MessageMode_SetCursor);
638 void Con_MessageMode2_f (void)
640 key_dest = key_message;
641 chat_mode = 1; // "say_team"
645 UIM_EnterBuffer(chat_buffer, sizeof(chat_buffer), 0, &Con_MessageMode_SetCursor);
653 void Con_CommandMode_f (void)
655 key_dest = key_message;
658 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
659 chat_bufferlen = strlen(chat_buffer);
661 chat_mode = -1; // command
663 UIM_EnterBuffer(chat_buffer, sizeof(chat_buffer), 0, &Con_MessageMode_SetCursor);
671 void Con_CheckResize (void)
676 f = bound(1, con_textsize.value, 128);
677 if(f != con_textsize.value)
678 Cvar_SetValueQuick(&con_textsize, f);
679 width = (int)floor(vid_conwidth.value / con_textsize.value);
680 width = bound(1, width, con.textsize/4);
681 // FIXME uses con in a non abstracted way
683 if (width == con_linewidth)
686 con_linewidth = width;
688 for(i = 0; i < CON_LINES_COUNT; ++i)
689 CON_LINES(i).height = -1; // recalculate when next needed
695 //[515]: the simplest command ever
696 //LordHavoc: not so simple after I made it print usage...
697 static void Con_Maps_f (void)
701 Con_Printf("usage: maps [mapnameprefix]\n");
704 else if (Cmd_Argc() == 2)
705 GetMapList(Cmd_Argv(1), NULL, 0);
707 GetMapList("", NULL, 0);
710 void Con_ConDump_f (void)
716 Con_Printf("usage: condump <filename>\n");
719 file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
722 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
725 for(i = 0; i < CON_LINES_COUNT; ++i)
727 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
728 FS_Write(file, "\n", 1);
733 void Con_Clear_f (void)
735 ConBuffer_Clear(&con);
746 ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
748 // Allocate a log queue, this will be freed after configs are parsed
749 logq_size = MAX_INPUTLINE;
750 logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
753 Cvar_RegisterVariable (&sys_colortranslation);
754 Cvar_RegisterVariable (&sys_specialcharactertranslation);
756 Cvar_RegisterVariable (&log_file);
757 Cvar_RegisterVariable (&log_dest_udp);
759 // support for the classic Quake option
760 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
761 if (COM_CheckParm ("-condebug") != 0)
762 Cvar_SetQuick (&log_file, "qconsole.log");
764 // register our cvars
765 Cvar_RegisterVariable (&con_chat);
766 Cvar_RegisterVariable (&con_chatpos);
767 Cvar_RegisterVariable (&con_chatsize);
768 Cvar_RegisterVariable (&con_chattime);
769 Cvar_RegisterVariable (&con_chatwidth);
770 Cvar_RegisterVariable (&con_notify);
771 Cvar_RegisterVariable (&con_notifyalign);
772 Cvar_RegisterVariable (&con_notifysize);
773 Cvar_RegisterVariable (&con_notifytime);
774 Cvar_RegisterVariable (&con_textsize);
775 Cvar_RegisterVariable (&con_chatsound);
778 Cvar_RegisterVariable (&con_nickcompletion);
779 Cvar_RegisterVariable (&con_nickcompletion_flags);
781 Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
782 Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
783 Cvar_RegisterVariable (&con_completion_exec); // *.cfg
785 // register our commands
786 Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
787 Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
788 Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
789 Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
790 Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
791 Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
792 Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
794 con_initialized = true;
795 Con_DPrint("Console initialized.\n");
798 void Con_Shutdown (void)
800 ConBuffer_Shutdown(&con);
807 Handles cursor positioning, line wrapping, etc
808 All console printing must go through this in order to be displayed
809 If no console is visible, the notify window will pop up.
812 void Con_PrintToHistory(const char *txt, int mask)
815 // \n goes to next line
816 // \r deletes current line and makes a new one
818 static int cr_pending = 0;
819 static char buf[CON_TEXTSIZE];
820 static int bufpos = 0;
822 if(!con.text) // FIXME uses a non-abstracted property of con
829 ConBuffer_DeleteLastLine(&con);
837 ConBuffer_AddLine(&con, buf, bufpos, mask);
842 ConBuffer_AddLine(&con, buf, bufpos, mask);
846 buf[bufpos++] = *txt;
847 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
849 ConBuffer_AddLine(&con, buf, bufpos, mask);
857 /*! The translation table between the graphical font and plain ASCII --KB */
858 static char qfont_table[256] = {
859 '\0', '#', '#', '#', '#', '.', '#', '#',
860 '#', 9, 10, '#', ' ', 13, '.', '.',
861 '[', ']', '0', '1', '2', '3', '4', '5',
862 '6', '7', '8', '9', '.', '<', '=', '>',
863 ' ', '!', '"', '#', '$', '%', '&', '\'',
864 '(', ')', '*', '+', ',', '-', '.', '/',
865 '0', '1', '2', '3', '4', '5', '6', '7',
866 '8', '9', ':', ';', '<', '=', '>', '?',
867 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
868 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
869 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
870 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
871 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
872 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
873 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
874 'x', 'y', 'z', '{', '|', '}', '~', '<',
876 '<', '=', '>', '#', '#', '.', '#', '#',
877 '#', '#', ' ', '#', ' ', '>', '.', '.',
878 '[', ']', '0', '1', '2', '3', '4', '5',
879 '6', '7', '8', '9', '.', '<', '=', '>',
880 ' ', '!', '"', '#', '$', '%', '&', '\'',
881 '(', ')', '*', '+', ',', '-', '.', '/',
882 '0', '1', '2', '3', '4', '5', '6', '7',
883 '8', '9', ':', ';', '<', '=', '>', '?',
884 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
885 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
886 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
887 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
888 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
889 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
890 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
891 'x', 'y', 'z', '{', '|', '}', '~', '<'
894 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
896 rcon_redirect_sock = sock;
897 rcon_redirect_dest = dest;
898 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
899 rcon_redirect_bufferpos = 5;
902 void Con_Rcon_Redirect_Flush(void)
904 rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
905 NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
906 memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
907 rcon_redirect_bufferpos = 5;
910 void Con_Rcon_Redirect_End(void)
912 Con_Rcon_Redirect_Flush();
913 rcon_redirect_dest = NULL;
914 rcon_redirect_sock = NULL;
917 void Con_Rcon_Redirect_Abort(void)
919 rcon_redirect_dest = NULL;
920 rcon_redirect_sock = NULL;
928 /// Adds a character to the rcon buffer.
929 void Con_Rcon_AddChar(int c)
931 if(log_dest_buffer_appending)
933 ++log_dest_buffer_appending;
935 // if this print is in response to an rcon command, add the character
936 // to the rcon redirect buffer
938 if (rcon_redirect_dest)
940 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
941 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
942 Con_Rcon_Redirect_Flush();
944 else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
946 if(log_dest_buffer_pos == 0)
947 Log_DestBuffer_Init();
948 log_dest_buffer[log_dest_buffer_pos++] = c;
949 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
950 Log_DestBuffer_Flush();
953 log_dest_buffer_pos = 0;
955 --log_dest_buffer_appending;
959 * Convert an RGB color to its nearest quake color.
960 * I'll cheat on this a bit by translating the colors to HSV first,
961 * S and V decide if it's black or white, otherwise, H will decide the
963 * @param _r Red (0-255)
964 * @param _g Green (0-255)
965 * @param _b Blue (0-255)
966 * @return A quake color character.
968 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
970 float r = ((float)_r)/255.0;
971 float g = ((float)_g)/255.0;
972 float b = ((float)_b)/255.0;
973 float min = min(r, min(g, b));
974 float max = max(r, max(g, b));
976 int h; ///< Hue angle [0,360]
977 float s; ///< Saturation [0,1]
978 float v = max; ///< In HSV v == max [0,1]
985 // Saturation threshold. We now say 0.2 is the minimum value for a color!
988 // If the value is less than half, return a black color code.
989 // Otherwise return a white one.
995 // Let's get the hue angle to define some colors:
999 h = (int)(60.0 * (g-b)/(max-min))%360;
1001 h = (int)(60.0 * (b-r)/(max-min) + 120);
1002 else // if(max == b) redundant check
1003 h = (int)(60.0 * (r-g)/(max-min) + 240);
1005 if(h < 36) // *red* to orange
1007 else if(h < 80) // orange over *yellow* to evilish-bright-green
1009 else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1011 else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1013 else if(h < 270) // darkish blue over *dark blue* to cool purple
1015 else if(h < 330) // cool purple over *purple* to ugly swiny red
1017 else // ugly red to red closes the circly
1026 extern cvar_t timestamps;
1027 extern cvar_t timeformat;
1028 extern qboolean sys_nostdout;
1029 void Con_Print(const char *msg)
1031 static int mask = 0;
1032 static int index = 0;
1033 static char line[MAX_INPUTLINE];
1037 Con_Rcon_AddChar(*msg);
1038 // if this is the beginning of a new line, print timestamp
1041 const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1043 // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1044 line[index++] = STRING_COLOR_TAG;
1045 // assert( STRING_COLOR_DEFAULT < 10 )
1046 line[index++] = STRING_COLOR_DEFAULT + '0';
1047 // special color codes for chat messages must always come first
1048 // for Con_PrintToHistory to work properly
1049 if (*msg == 1 || *msg == 2)
1054 if (con_chatsound.value)
1056 if(gamemode == GAME_NEXUIZ)
1058 if(msg[1] == '\r' && cl.foundtalk2wav)
1059 S_LocalSound ("sound/misc/talk2.wav");
1061 S_LocalSound ("sound/misc/talk.wav");
1065 if (msg[1] == '(' && cl.foundtalk2wav)
1066 S_LocalSound ("sound/misc/talk2.wav");
1068 S_LocalSound ("sound/misc/talk.wav");
1071 mask = CON_MASK_CHAT;
1073 line[index++] = STRING_COLOR_TAG;
1074 line[index++] = '3';
1076 Con_Rcon_AddChar(*msg);
1079 for (;*timestamp;index++, timestamp++)
1080 if (index < (int)sizeof(line) - 2)
1081 line[index] = *timestamp;
1083 // append the character
1084 line[index++] = *msg;
1085 // if this is a newline character, we have a complete line to print
1086 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1088 // terminate the line
1092 // send to scrollable buffer
1093 if (con_initialized && cls.state != ca_dedicated)
1095 Con_PrintToHistory(line, mask);
1098 // send to terminal or dedicated server window
1102 if(sys_specialcharactertranslation.integer)
1104 for (p = (unsigned char *) line;*p; p++)
1105 *p = qfont_table[*p];
1108 if(sys_colortranslation.integer == 1) // ANSI
1110 static char printline[MAX_INPUTLINE * 4 + 3];
1111 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1112 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1117 for(in = line, out = printline; *in; ++in)
1121 case STRING_COLOR_TAG:
1122 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1124 char r = tolower(in[2]);
1125 char g = tolower(in[3]);
1126 char b = tolower(in[4]);
1127 // it's a hex digit already, so the else part needs no check --blub
1128 if(isdigit(r)) r -= '0';
1130 if(isdigit(g)) g -= '0';
1132 if(isdigit(b)) b -= '0';
1135 color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1136 in += 3; // 3 only, the switch down there does the fourth
1143 case STRING_COLOR_TAG:
1145 *out++ = STRING_COLOR_TAG;
1151 if(lastcolor == 0) break; else lastcolor = 0;
1152 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1157 if(lastcolor == 1) break; else lastcolor = 1;
1158 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1163 if(lastcolor == 2) break; else lastcolor = 2;
1164 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1169 if(lastcolor == 3) break; else lastcolor = 3;
1170 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1175 if(lastcolor == 4) break; else lastcolor = 4;
1176 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1181 if(lastcolor == 5) break; else lastcolor = 5;
1182 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1187 if(lastcolor == 6) break; else lastcolor = 6;
1188 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1193 // bold normal color
1195 if(lastcolor == 8) break; else lastcolor = 8;
1196 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1199 *out++ = STRING_COLOR_TAG;
1206 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1223 Sys_PrintToTerminal(printline);
1225 else if(sys_colortranslation.integer == 2) // Quake
1227 Sys_PrintToTerminal(line);
1231 static char printline[MAX_INPUTLINE]; // it can only get shorter here
1234 for(in = line, out = printline; *in; ++in)
1238 case STRING_COLOR_TAG:
1241 case STRING_COLOR_RGB_TAG_CHAR:
1242 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1247 *out++ = STRING_COLOR_TAG;
1248 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1251 case STRING_COLOR_TAG:
1253 *out++ = STRING_COLOR_TAG;
1268 *out++ = STRING_COLOR_TAG;
1278 Sys_PrintToTerminal(printline);
1281 // empty the line buffer
1293 void Con_Printf(const char *fmt, ...)
1296 char msg[MAX_INPUTLINE];
1298 va_start(argptr,fmt);
1299 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1310 void Con_DPrint(const char *msg)
1312 if (!developer.integer)
1313 return; // don't confuse non-developers with techie stuff...
1322 void Con_DPrintf(const char *fmt, ...)
1325 char msg[MAX_INPUTLINE];
1327 if (!developer.integer)
1328 return; // don't confuse non-developers with techie stuff...
1330 va_start(argptr,fmt);
1331 dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1339 ==============================================================================
1343 ==============================================================================
1350 The input line scrolls horizontally if typing goes beyond the right edge
1352 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1355 void Con_DrawInput (void)
1359 char editlinecopy[MAX_INPUTLINE+1], *text;
1362 if (!key_consoleactive)
1363 return; // don't draw anything
1365 strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1366 text = editlinecopy;
1368 // Advanced Console Editing by Radix radix@planetquake.com
1369 // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1370 // use strlen of edit_line instead of key_linepos to allow editing
1371 // of early characters w/o erasing
1373 y = (int)strlen(text);
1375 // fill out remainder with spaces
1376 for (i = y; i < (int)sizeof(editlinecopy)-1; i++)
1379 // add the cursor frame
1380 if ((int)(realtime*con_cursorspeed) & 1) // cursor is visible
1382 if (!utf8_enable.integer)
1383 text[key_linepos] = 11 + 130 * key_insert; // either solid or triangle facing right
1384 else if (y + 3 < (int)sizeof(editlinecopy)-1)
1386 int ofs = u8_bytelen(text + key_linepos, 1);
1389 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1393 memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1394 memcpy(text + key_linepos, curbuf, len);
1397 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1400 // text[key_linepos + 1] = 0;
1402 x = vid_conwidth.value * 0.95 - DrawQ_TextWidth_Font(text, key_linepos, false, FONT_CONSOLE) * con_textsize.value;
1407 DrawQ_String_Font(x, con_vislines - con_textsize.value*2, text, 0, con_textsize.value, con_textsize.value, 1.0, 1.0, 1.0, 1.0, 0, NULL, false, FONT_CONSOLE );
1410 // key_line[key_linepos] = 0;
1416 float alignment; // 0 = left, 0.5 = center, 1 = right
1422 const char *continuationString;
1425 int colorindex; // init to -1
1429 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1431 con_text_info_t *ti = (con_text_info_t *) passthrough;
1434 ti->colorindex = -1;
1435 return ti->fontsize * ti->font->maxwidth;
1438 return DrawQ_TextWidth_Font_UntilWidth_Size(w, ti->fontsize, ti->fontsize, length, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1439 else if(maxWidth == -1)
1440 return DrawQ_TextWidth_Font_Size(w, ti->fontsize, ti->fontsize, *length, false, ti->font);
1443 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1444 // Note: this is NOT a Con_Printf, as it could print recursively
1449 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1455 (void) isContinuation;
1459 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1461 con_text_info_t *ti = (con_text_info_t *) passthrough;
1463 if(ti->y < ti->ymin - 0.001)
1465 else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1469 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1470 if(isContinuation && *ti->continuationString)
1471 x += (int) DrawQ_String_Font(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);
1473 DrawQ_String_Font(x, ti->y, line, length, ti->fontsize, ti->fontsize, 1.0, 1.0, 1.0, 1.0, 0, &(ti->colorindex), false, ti->font);
1476 ti->y += ti->fontsize;
1480 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)
1484 int maxlines = (int) floor(height / fontsize + 0.01f);
1487 int continuationWidth = 0;
1489 double t = cl.time; // saved so it won't change
1492 ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1493 ti.fontsize = fontsize;
1494 ti.alignment = alignment_x;
1497 ti.ymax = y + height;
1498 ti.continuationString = continuationString;
1501 Con_WordWidthFunc(&ti, NULL, &l, -1);
1502 l = strlen(continuationString);
1503 continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1505 // first find the first line to draw by backwards iterating and word wrapping to find their length...
1506 startidx = CON_LINES_COUNT;
1507 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1509 con_lineinfo_t *l = &CON_LINES(i);
1512 if((l->mask & mask_must) != mask_must)
1514 if(l->mask & mask_mustnot)
1516 if(maxage && (l->addtime < t - maxage))
1520 // Calculate its actual height...
1521 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1522 if(lines + mylines >= maxlines)
1524 nskip = lines + mylines - maxlines;
1533 // then center according to the calculated amount of lines...
1535 ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1537 // then actually draw
1538 for(i = startidx; i < CON_LINES_COUNT; ++i)
1540 con_lineinfo_t *l = &CON_LINES(i);
1542 if((l->mask & mask_must) != mask_must)
1544 if(l->mask & mask_mustnot)
1546 if(maxage && (l->addtime < t - maxage))
1549 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1559 Draws the last few lines of output transparently over the game top
1562 void Con_DrawNotify (void)
1565 float chatstart, notifystart, inputsize;
1567 char temptext[MAX_INPUTLINE];
1571 ConBuffer_FixTimes(&con);
1573 numChatlines = con_chat.integer;
1574 chatpos = con_chatpos.integer;
1576 if (con_notify.integer < 0)
1577 Cvar_SetValueQuick(&con_notify, 0);
1578 if (gamemode == GAME_TRANSFUSION)
1579 v = 8; // vertical offset
1583 // GAME_NEXUIZ: center, otherwise left justify
1584 align = con_notifyalign.value;
1585 if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1587 if(gamemode == GAME_NEXUIZ)
1595 // first chat, input line, then notify
1597 notifystart = v + (numChatlines + 1) * con_chatsize.value;
1599 else if(chatpos > 0)
1601 // first notify, then (chatpos-1) empty lines, then chat, then input
1603 chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1605 else // if(chatpos < 0)
1607 // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1609 chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1614 // just notify and input
1616 chatstart = 0; // shut off gcc warning
1619 v = notifystart + con_notifysize.value * Con_DrawNotifyRect(0, CON_MASK_INPUT | CON_MASK_HIDENOTIFY | (numChatlines ? CON_MASK_CHAT : 0), con_notifytime.value, 0, notifystart, vid_conwidth.value, con_notify.value * con_notifysize.value, con_notifysize.value, align, 0.0, "");
1624 v = chatstart + numChatlines * con_chatsize.value;
1625 Con_DrawNotifyRect(CON_MASK_CHAT, CON_MASK_INPUT, con_chattime.value, 0, chatstart, vid_conwidth.value * con_chatwidth.value, v - chatstart, 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
1628 if (key_dest == key_message)
1630 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1631 int colorindex = -1;
1633 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1635 // LordHavoc: speedup, and other improvements
1637 dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1639 dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1641 dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1644 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1645 x = vid_conwidth.value - DrawQ_TextWidth_Font(temptext, 0, false, FONT_CHAT) * inputsize;
1648 DrawQ_String_Font(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1654 Con_MeasureConsoleLine
1656 Counts the number of lines for a line on the console.
1659 int Con_MeasureConsoleLine(int lineno)
1661 float width = vid_conwidth.value;
1663 con_lineinfo_t *li = &CON_LINES(lineno);
1665 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1668 ti.fontsize = con_textsize.value;
1669 ti.font = FONT_CONSOLE;
1671 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1678 Returns the height of a given console line; calculates it if necessary.
1681 int Con_LineHeight(int i)
1683 con_lineinfo_t *li = &CON_LINES(i);
1687 return li->height = Con_MeasureConsoleLine(i);
1694 Draws a line of the console; returns its height in lines.
1695 If alpha is 0, the line is not drawn, but still wrapped and its height
1699 int Con_DrawConsoleLine(float y, int lineno, float ymin, float ymax)
1701 float width = vid_conwidth.value;
1703 con_lineinfo_t *li = &CON_LINES(lineno);
1705 //if(con.lines[lineno].mask & CON_MASK_LOADEDHISTORY)
1708 ti.continuationString = "";
1710 ti.fontsize = con_textsize.value;
1711 ti.font = FONT_CONSOLE;
1713 ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1718 return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1725 Calculates the last visible line index and how much to show of it based on
1729 void Con_LastVisibleLine(int *last, int *limitlast)
1734 if(con_backscroll < 0)
1737 // now count until we saw con_backscroll actual lines
1738 for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1740 int h = Con_LineHeight(i);
1742 // line is the last visible line?
1743 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1746 *limitlast = lines_seen + h - con_backscroll;
1753 // if we get here, no line was on screen - scroll so that one line is
1755 con_backscroll = lines_seen - 1;
1756 *last = con.lines_first;
1757 // FIXME uses con in a non abstracted way
1765 Draws the console with the solid background
1766 The typing input line at the bottom should only be drawn if typing is allowed
1769 void Con_DrawConsole (int lines)
1771 int i, last, limitlast;
1777 con_vislines = lines;
1779 // draw the background
1780 DrawQ_Pic(0, lines - vid_conheight.integer, scr_conbrightness.value >= 0.01f ? Draw_CachePic ("gfx/conback") : NULL, vid_conwidth.integer, vid_conheight.integer, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, cls.signon == SIGNONS ? scr_conalpha.value : 1.0, 0); // always full alpha when not in game
1781 DrawQ_String_Font(vid_conwidth.integer - DrawQ_TextWidth_Font(engineversion, 0, false, FONT_CONSOLE) * con_textsize.value, lines - con_textsize.value, engineversion, 0, con_textsize.value, con_textsize.value, 1, 0, 0, 1, 0, NULL, true, FONT_CONSOLE);
1784 if(CON_LINES_COUNT > 0)
1786 float ymax = con_vislines - 2 * con_textsize.value;
1787 Con_LastVisibleLine(&last, &limitlast);
1788 y = ymax - con_textsize.value;
1791 y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1792 // FIXME uses con in a non abstracted way
1797 y -= Con_DrawConsoleLine(y, i, 0, ymax) * con_textsize.value;
1799 break; // top of console buffer
1801 break; // top of console window
1807 // draw the input prompt, user text, and cursor if desired
1815 Prints not only map filename, but also
1816 its format (q1/q2/q3/hl) and even its message
1818 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1819 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1820 //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
1821 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1822 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1826 int i, k, max, p, o, min;
1829 unsigned char buf[1024];
1831 dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1832 t = FS_Search(message, 1, true);
1835 if (t->numfilenames > 1)
1836 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1837 len = (unsigned char *)Z_Malloc(t->numfilenames);
1839 for(max=i=0;i<t->numfilenames;i++)
1841 k = (int)strlen(t->filenames[i]);
1851 for(i=0;i<t->numfilenames;i++)
1853 int lumpofs = 0, lumplen = 0;
1854 char *entities = NULL;
1855 const char *data = NULL;
1857 char entfilename[MAX_QPATH];
1858 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1860 f = FS_OpenVirtualFile(t->filenames[i], true);
1863 memset(buf, 0, 1024);
1864 FS_Read(f, buf, 1024);
1865 if (!memcmp(buf, "IBSP", 4))
1867 p = LittleLong(((int *)buf)[1]);
1868 if (p == Q3BSPVERSION)
1870 q3dheader_t *header = (q3dheader_t *)buf;
1871 lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
1872 lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
1874 else if (p == Q2BSPVERSION)
1876 q2dheader_t *header = (q2dheader_t *)buf;
1877 lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
1878 lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
1881 else if((p = LittleLong(((int *)buf)[0])) == BSPVERSION || p == 30)
1883 dheader_t *header = (dheader_t *)buf;
1884 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
1885 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
1889 strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
1890 memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
1891 entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
1892 if (!entities && lumplen >= 10)
1894 FS_Seek(f, lumpofs, SEEK_SET);
1895 entities = (char *)Z_Malloc(lumplen + 1);
1896 FS_Read(f, entities, lumplen);
1900 // if there are entities to parse, a missing message key just
1901 // means there is no title, so clear the message string now
1907 if (!COM_ParseToken_Simple(&data, false, false))
1909 if (com_token[0] == '{')
1911 if (com_token[0] == '}')
1913 // skip leading whitespace
1914 for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
1915 for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
1916 keyname[l] = com_token[k+l];
1918 if (!COM_ParseToken_Simple(&data, false, false))
1920 if (developer.integer >= 100)
1921 Con_Printf("key: %s %s\n", keyname, com_token);
1922 if (!strcmp(keyname, "message"))
1924 // get the message contents
1925 strlcpy(message, com_token, sizeof(message));
1935 *(t->filenames[i]+len[i]+5) = 0;
1938 case Q3BSPVERSION: strlcpy((char *)buf, "Q3", sizeof(buf));break;
1939 case Q2BSPVERSION: strlcpy((char *)buf, "Q2", sizeof(buf));break;
1940 case BSPVERSION: strlcpy((char *)buf, "Q1", sizeof(buf));break;
1941 case 30: strlcpy((char *)buf, "HL", sizeof(buf));break;
1942 default: strlcpy((char *)buf, "??", sizeof(buf));break;
1944 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
1949 k = *(t->filenames[0]+5+p);
1952 for(i=1;i<t->numfilenames;i++)
1953 if(*(t->filenames[i]+5+p) != k)
1957 if(p > o && completedname && completednamebufferlength > 0)
1959 memset(completedname, 0, completednamebufferlength);
1960 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
1970 New function for tab-completion system
1971 Added by EvilTypeGuy
1972 MEGA Thanks to Taniwha
1975 void Con_DisplayList(const char **list)
1977 int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
1978 const char **walk = list;
1981 len = (int)strlen(*walk);
1989 len = (int)strlen(*list);
1990 if (pos + maxlen >= width) {
1996 for (i = 0; i < (maxlen - len); i++)
2008 SanitizeString strips color tags from the string in
2009 and writes the result on string out
2011 void SanitizeString(char *in, char *out)
2015 if(*in == STRING_COLOR_TAG)
2020 out[0] = STRING_COLOR_TAG;
2024 else if (*in >= '0' && *in <= '9') // ^[0-9] found
2031 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2034 else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2036 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2043 } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2048 else if (*in != STRING_COLOR_TAG)
2051 *out = qfont_table[*(unsigned char*)in];
2058 // Now it becomes TRICKY :D --blub
2059 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // contains the nicks with colors and all that
2060 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME]; // sanitized list for completion when there are other possible matches.
2061 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2062 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
2063 static int Nicks_matchpos;
2065 // co against <<:BLASTER:>> is true!?
2066 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2070 if(tolower(*a) == tolower(*b))
2084 return (*a < *b) ? -1 : 1;
2088 return (*a < *b) ? -1 : 1;
2092 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2095 if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2097 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2098 return Nicks_strncasecmp_nospaces(a, b, a_len);
2099 return strncasecmp(a, b, a_len);
2102 space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2104 // ignore non alphanumerics of B
2105 // if A contains a non-alphanumeric, B must contain it as well though!
2108 qboolean alnum_a, alnum_b;
2110 if(tolower(*a) == tolower(*b))
2112 if(*a == 0) // end of both strings, they're equal
2119 // not equal, end of one string?
2124 // ignore non alphanumerics
2125 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2126 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2127 if(!alnum_a) // b must contain this
2128 return (*a < *b) ? -1 : 1;
2131 // otherwise, both are alnum, they're just not equal, return the appropriate number
2133 return (*a < *b) ? -1 : 1;
2139 /* Nicks_CompleteCountPossible
2141 Count the number of possible nicks to complete
2143 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2152 if(!con_nickcompletion.integer)
2155 // changed that to 1
2156 if(!line[0])// || !line[1]) // we want at least... 2 written characters
2159 for(i = 0; i < cl.maxclients; ++i)
2162 if(!cl.scores[p].name[0])
2165 SanitizeString(cl.scores[p].name, name);
2166 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2171 length = strlen(name);
2173 spos = pos - 1; // no need for a minimum of characters :)
2177 if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2179 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2180 !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2186 if(isCon && spos == 0)
2188 if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2194 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2195 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2197 // the sanitized list
2198 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2201 Nicks_matchpos = match;
2204 Nicks_offset[count] = s - (&line[match]);
2205 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2212 void Cmd_CompleteNicksPrint(int count)
2215 for(i = 0; i < count; ++i)
2216 Con_Printf("%s\n", Nicks_list[i]);
2219 void Nicks_CutMatchesNormal(int count)
2221 // cut match 0 down to the longest possible completion
2224 c = strlen(Nicks_sanlist[0]) - 1;
2225 for(i = 1; i < count; ++i)
2227 l = strlen(Nicks_sanlist[i]) - 1;
2231 for(l = 0; l <= c; ++l)
2232 if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2238 Nicks_sanlist[0][c+1] = 0;
2239 //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2242 unsigned int Nicks_strcleanlen(const char *s)
2247 if( (*s >= 'a' && *s <= 'z') ||
2248 (*s >= 'A' && *s <= 'Z') ||
2249 (*s >= '0' && *s <= '9') ||
2257 void Nicks_CutMatchesAlphaNumeric(int count)
2259 // cut match 0 down to the longest possible completion
2262 char tempstr[sizeof(Nicks_sanlist[0])];
2264 char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2266 c = strlen(Nicks_sanlist[0]);
2267 for(i = 0, l = 0; i < (int)c; ++i)
2269 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2270 (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2271 (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2273 tempstr[l++] = Nicks_sanlist[0][i];
2278 for(i = 1; i < count; ++i)
2281 b = Nicks_sanlist[i];
2291 if(tolower(*a) == tolower(*b))
2297 if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2299 // b is alnum, so cut
2306 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2307 Nicks_CutMatchesNormal(count);
2308 //if(!Nicks_sanlist[0][0])
2309 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2311 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2312 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2316 void Nicks_CutMatchesNoSpaces(int count)
2318 // cut match 0 down to the longest possible completion
2321 char tempstr[sizeof(Nicks_sanlist[0])];
2324 c = strlen(Nicks_sanlist[0]);
2325 for(i = 0, l = 0; i < (int)c; ++i)
2327 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2329 tempstr[l++] = Nicks_sanlist[0][i];
2334 for(i = 1; i < count; ++i)
2337 b = Nicks_sanlist[i];
2347 if(tolower(*a) == tolower(*b))
2361 // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2362 Nicks_CutMatchesNormal(count);
2363 //if(!Nicks_sanlist[0][0])
2364 //Con_Printf("TS: %s\n", tempstr);
2365 if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2367 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2368 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2372 void Nicks_CutMatches(int count)
2374 if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2375 Nicks_CutMatchesAlphaNumeric(count);
2376 else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2377 Nicks_CutMatchesNoSpaces(count);
2379 Nicks_CutMatchesNormal(count);
2382 const char **Nicks_CompleteBuildList(int count)
2386 // the list is freed by Con_CompleteCommandLine, so create a char**
2387 buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2389 for(; bpos < count; ++bpos)
2390 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2392 Nicks_CutMatches(count);
2400 Restores the previous used color, after the autocompleted name.
2402 int Nicks_AddLastColor(char *buffer, int pos)
2404 qboolean quote_added = false;
2406 int color = STRING_COLOR_DEFAULT + '0';
2407 char r = 0, g = 0, b = 0;
2409 if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2411 // we'll have to add a quote :)
2412 buffer[pos++] = '\"';
2416 if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2418 // add color when no quote was added, or when flags &4?
2420 for(match = Nicks_matchpos-1; match >= 0; --match)
2422 if(buffer[match] == STRING_COLOR_TAG)
2424 if( isdigit(buffer[match+1]) )
2426 color = buffer[match+1];
2429 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2431 if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2433 r = buffer[match+2];
2434 g = buffer[match+3];
2435 b = buffer[match+4];
2444 if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2446 else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2447 && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2450 buffer[pos++] = STRING_COLOR_TAG;
2453 buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2459 buffer[pos++] = color;
2464 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2467 /*if(!con_nickcompletion.integer)
2468 return; is tested in Nicks_CompletionCountPossible */
2469 n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2475 msg = Nicks_list[0];
2476 len = min(size - Nicks_matchpos - 3, strlen(msg));
2477 memcpy(&buffer[Nicks_matchpos], msg, len);
2478 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2479 len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2480 buffer[len++] = ' ';
2487 Con_Printf("\n%i possible nicks:\n", n);
2488 Cmd_CompleteNicksPrint(n);
2490 Nicks_CutMatches(n);
2492 msg = Nicks_sanlist[0];
2493 len = min(size - Nicks_matchpos, strlen(msg));
2494 memcpy(&buffer[Nicks_matchpos], msg, len);
2495 buffer[Nicks_matchpos + len] = 0;
2497 return Nicks_matchpos + len;
2504 Con_CompleteCommandLine
2506 New function for tab-completion system
2507 Added by EvilTypeGuy
2508 Thanks to Fett erich@heintz.com
2510 Enhanced to tab-complete map names by [515]
2513 void Con_CompleteCommandLine (void)
2515 const char *cmd = "";
2517 const char **list[4] = {0, 0, 0, 0};
2520 int c, v, a, i, cmd_len, pos, k;
2521 int n; // nicks --blub
2522 const char *space, *patterns;
2524 //find what we want to complete
2529 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2535 strlcpy(s2, key_line + key_linepos, sizeof(s2)); //save chars after cursor
2536 key_line[key_linepos] = 0; //hide them
2538 space = strchr(key_line + 1, ' ');
2539 if(space && pos == (space - key_line) + 1)
2541 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2543 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2544 if(patterns && !*patterns)
2545 patterns = NULL; // get rid of the empty string
2547 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2551 if (GetMapList(s, t, sizeof(t)))
2553 // first move the cursor
2554 key_linepos += (int)strlen(t) - (int)strlen(s);
2556 // and now do the actual work
2558 strlcat(key_line, t, MAX_INPUTLINE);
2559 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2561 // and fix the cursor
2562 if(key_linepos > (int) strlen(key_line))
2563 key_linepos = (int) strlen(key_line);
2572 stringlist_t resultbuf, dirbuf;
2575 // // store completion patterns (space separated) for command foo in con_completion_foo
2576 // set con_completion_foo "foodata/*.foodefault *.foo"
2579 // Note: patterns with slash are always treated as absolute
2580 // patterns; patterns without slash search in the innermost
2581 // directory the user specified. There is no way to "complete into"
2582 // a directory as of now, as directories seem to be unknown to the
2586 // set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2587 // set con_completion_playdemo "*.dem"
2588 // set con_completion_play "*.wav *.ogg"
2590 // TODO somehow add support for directories; these shall complete
2591 // to their name + an appended slash.
2593 stringlistinit(&resultbuf);
2594 stringlistinit(&dirbuf);
2595 while(COM_ParseToken_Simple(&patterns, false, false))
2598 if(strchr(com_token, '/'))
2600 search = FS_Search(com_token, true, true);
2604 const char *slash = strrchr(s, '/');
2607 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2608 strlcat(t, com_token, sizeof(t));
2609 search = FS_Search(t, true, true);
2612 search = FS_Search(com_token, true, true);
2616 for(i = 0; i < search->numfilenames; ++i)
2617 if(!strncmp(search->filenames[i], s, strlen(s)))
2618 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2619 stringlistappend(&resultbuf, search->filenames[i]);
2620 FS_FreeSearch(search);
2624 // In any case, add directory names
2627 const char *slash = strrchr(s, '/');
2630 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2631 strlcat(t, "*", sizeof(t));
2632 search = FS_Search(t, true, true);
2635 search = FS_Search("*", true, true);
2638 for(i = 0; i < search->numfilenames; ++i)
2639 if(!strncmp(search->filenames[i], s, strlen(s)))
2640 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2641 stringlistappend(&dirbuf, search->filenames[i]);
2642 FS_FreeSearch(search);
2646 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2649 unsigned int matchchars;
2650 if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2652 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2655 if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2657 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2661 stringlistsort(&resultbuf); // dirbuf is already sorted
2662 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2663 for(i = 0; i < dirbuf.numstrings; ++i)
2665 Con_Printf("%s/\n", dirbuf.strings[i]);
2667 for(i = 0; i < resultbuf.numstrings; ++i)
2669 Con_Printf("%s\n", resultbuf.strings[i]);
2671 matchchars = sizeof(t) - 1;
2672 if(resultbuf.numstrings > 0)
2674 p = resultbuf.strings[0];
2675 q = resultbuf.strings[resultbuf.numstrings - 1];
2676 for(; *p && *p == *q; ++p, ++q);
2677 matchchars = (unsigned int)(p - resultbuf.strings[0]);
2679 if(dirbuf.numstrings > 0)
2681 p = dirbuf.strings[0];
2682 q = dirbuf.strings[dirbuf.numstrings - 1];
2683 for(; *p && *p == *q; ++p, ++q);
2684 matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2686 // now p points to the first non-equal character, or to the end
2687 // of resultbuf.strings[0]. We want to append the characters
2688 // from resultbuf.strings[0] to (not including) p as these are
2689 // the unique prefix
2690 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2693 // first move the cursor
2694 key_linepos += (int)strlen(t) - (int)strlen(s);
2696 // and now do the actual work
2698 strlcat(key_line, t, MAX_INPUTLINE);
2699 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2701 // and fix the cursor
2702 if(key_linepos > (int) strlen(key_line))
2703 key_linepos = (int) strlen(key_line);
2705 stringlistfreecontents(&resultbuf);
2706 stringlistfreecontents(&dirbuf);
2708 return; // bail out, when we complete for a command that wants a file name
2713 // Count number of possible matches and print them
2714 c = Cmd_CompleteCountPossible(s);
2717 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2718 Cmd_CompleteCommandPrint(s);
2720 v = Cvar_CompleteCountPossible(s);
2723 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2724 Cvar_CompleteCvarPrint(s);
2726 a = Cmd_CompleteAliasCountPossible(s);
2729 Con_Printf("\n%i possible aliases%s\n", a, (a > 1) ? "s: " : ":");
2730 Cmd_CompleteAliasPrint(s);
2732 n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2735 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2736 Cmd_CompleteNicksPrint(n);
2739 if (!(c + v + a + n)) // No possible matches
2742 strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2747 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2749 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2751 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2753 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2755 for (cmd_len = (int)strlen(s);;cmd_len++)
2758 for (i = 0; i < 3; i++)
2760 for (l = list[i];*l;l++)
2761 if ((*l)[cmd_len] != cmd[cmd_len])
2763 // all possible matches share this character, so we continue...
2766 // if all matches ended at the same position, stop
2767 // (this means there is only one match)
2773 // prevent a buffer overrun by limiting cmd_len according to remaining space
2774 cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2778 memcpy(&key_line[key_linepos], cmd, cmd_len);
2779 key_linepos += cmd_len;
2780 // if there is only one match, add a space after it
2781 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2784 { // was a nick, might have an offset, and needs colors ;) --blub
2785 key_linepos = pos - Nicks_offset[0];
2786 cmd_len = strlen(Nicks_list[0]);
2787 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2789 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2790 key_linepos += cmd_len;
2791 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2792 key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2794 key_line[key_linepos++] = ' ';
2798 // use strlcat to avoid a buffer overrun
2799 key_line[key_linepos] = 0;
2800 strlcat(key_line, s2, sizeof(key_line));
2802 // free the command, cvar, and alias lists
2803 for (i = 0; i < 4; i++)
2805 Mem_Free((void *)list[i]);