]> icculus.org git repositories - divverent/darkplaces.git/blob - console.c
try fixing the SSE skeletonobjects case
[divverent/darkplaces.git] / console.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
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.
8
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.
12
13 See the GNU General Public License for more details.
14
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.
18
19 */
20 // console.c
21
22 #if !defined(WIN32) || defined(__MINGW32__)
23 # include <unistd.h>
24 #endif
25 #include <time.h>
26
27 #include "quakedef.h"
28
29 // for u8_encodech
30 #include "ft2.h"
31
32 float con_cursorspeed = 4;
33
34 // lines up from bottom to display
35 int con_backscroll;
36
37 conbuffer_t con;
38
39 #define CON_LINES(i) CONBUFFER_LINES(&con, i)
40 #define CON_LINES_LAST CONBUFFER_LINES_LAST(&con)
41 #define CON_LINES_COUNT CONBUFFER_LINES_COUNT(&con)
42
43 cvar_t con_notifytime = {CVAR_SAVE, "con_notifytime","3", "how long notify lines last, in seconds"};
44 cvar_t con_notify = {CVAR_SAVE, "con_notify","4", "how many notify lines to show"};
45 cvar_t con_notifyalign = {CVAR_SAVE, "con_notifyalign", "", "how to align notify lines: 0 = left, 0.5 = center, 1 = right, empty string = game default)"};
46
47 cvar_t con_chattime = {CVAR_SAVE, "con_chattime","30", "how long chat lines last, in seconds"};
48 cvar_t con_chat = {CVAR_SAVE, "con_chat","0", "how many chat lines to show in a dedicated chat area"};
49 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)"};
50 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"};
51 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)"};
52 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)"};
53 cvar_t con_chatwidth = {CVAR_SAVE, "con_chatwidth","1.0", "relative chat window width"};
54 cvar_t con_textsize = {CVAR_SAVE, "con_textsize","8", "console text size in virtual 2D pixels"};
55 cvar_t con_notifysize = {CVAR_SAVE, "con_notifysize","8", "notify text size in virtual 2D pixels"};
56 cvar_t con_chatsize = {CVAR_SAVE, "con_chatsize","8", "chat text size in virtual 2D pixels (if con_chat is enabled)"};
57 cvar_t con_chatsound = {CVAR_SAVE, "con_chatsound","1", "enables chat sound to play on message"};
58
59
60 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)"};
61 #ifdef WIN32
62 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)"};
63 #else
64 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)"};
65 #endif
66
67
68 cvar_t con_nickcompletion = {CVAR_SAVE, "con_nickcompletion", "1", "tab-complete nicks in console and message input"};
69 cvar_t con_nickcompletion_flags = {CVAR_SAVE, "con_nickcompletion_flags", "11", "Bitfield: "
70                                    "0: add nothing after completion. "
71                                    "1: add the last color after completion. "
72                                    "2: add a quote when starting a quote instead of the color. "
73                                    "4: will replace 1, will force color, even after a quote. "
74                                    "8: ignore non-alphanumerics. "
75                                    "16: ignore spaces. "};
76 #define NICKS_ADD_COLOR 1
77 #define NICKS_ADD_QUOTE 2
78 #define NICKS_FORCE_COLOR 4
79 #define NICKS_ALPHANUMERICS_ONLY 8
80 #define NICKS_NO_SPACES 16
81
82 cvar_t con_completion_playdemo = {CVAR_SAVE, "con_completion_playdemo", "*.dem", "completion pattern for the playdemo command"};
83 cvar_t con_completion_timedemo = {CVAR_SAVE, "con_completion_timedemo", "*.dem", "completion pattern for the timedemo command"};
84 cvar_t con_completion_exec = {CVAR_SAVE, "con_completion_exec", "*.cfg", "completion pattern for the exec command"};
85
86 int con_linewidth;
87 int con_vislines;
88
89 qboolean con_initialized;
90
91 // used for server replies to rcon command
92 lhnetsocket_t *rcon_redirect_sock = NULL;
93 lhnetaddress_t *rcon_redirect_dest = NULL;
94 int rcon_redirect_bufferpos = 0;
95 char rcon_redirect_buffer[1400];
96
97 // generic functions for console buffers
98
99 void ConBuffer_Init(conbuffer_t *buf, int textsize, int maxlines, mempool_t *mempool)
100 {
101         buf->active = true;
102         buf->textsize = textsize;
103         buf->text = (char *) Mem_Alloc(mempool, textsize);
104         buf->maxlines = maxlines;
105         buf->lines = (con_lineinfo_t *) Mem_Alloc(mempool, maxlines * sizeof(*buf->lines));
106         buf->lines_first = 0;
107         buf->lines_count = 0;
108 }
109
110 /*
111 ================
112 ConBuffer_Clear
113 ================
114 */
115 void ConBuffer_Clear (conbuffer_t *buf)
116 {
117         buf->lines_count = 0;
118 }
119
120 /*
121 ================
122 ConBuffer_Shutdown
123 ================
124 */
125 void ConBuffer_Shutdown(conbuffer_t *buf)
126 {
127         buf->active = false;
128         Mem_Free(buf->text);
129         Mem_Free(buf->lines);
130         buf->text = NULL;
131         buf->lines = NULL;
132 }
133
134 /*
135 ================
136 ConBuffer_FixTimes
137
138 Notifies the console code about the current time
139 (and shifts back times of other entries when the time
140 went backwards)
141 ================
142 */
143 void ConBuffer_FixTimes(conbuffer_t *buf)
144 {
145         int i;
146         if(buf->lines_count >= 1)
147         {
148                 double diff = cl.time - CONBUFFER_LINES_LAST(buf).addtime;
149                 if(diff < 0)
150                 {
151                         for(i = 0; i < buf->lines_count; ++i)
152                                 CONBUFFER_LINES(buf, i).addtime += diff;
153                 }
154         }
155 }
156
157 /*
158 ================
159 ConBuffer_DeleteLine
160
161 Deletes the first line from the console history.
162 ================
163 */
164 void ConBuffer_DeleteLine(conbuffer_t *buf)
165 {
166         if(buf->lines_count == 0)
167                 return;
168         --buf->lines_count;
169         buf->lines_first = (buf->lines_first + 1) % buf->maxlines;
170 }
171
172 /*
173 ================
174 ConBuffer_DeleteLastLine
175
176 Deletes the last line from the console history.
177 ================
178 */
179 void ConBuffer_DeleteLastLine(conbuffer_t *buf)
180 {
181         if(buf->lines_count == 0)
182                 return;
183         --buf->lines_count;
184 }
185
186 /*
187 ================
188 ConBuffer_BytesLeft
189
190 Checks if there is space for a line of the given length, and if yes, returns a
191 pointer to the start of such a space, and NULL otherwise.
192 ================
193 */
194 static char *ConBuffer_BytesLeft(conbuffer_t *buf, int len)
195 {
196         if(len > buf->textsize)
197                 return NULL;
198         if(buf->lines_count == 0)
199                 return buf->text;
200         else
201         {
202                 char *firstline_start = buf->lines[buf->lines_first].start;
203                 char *lastline_onepastend = CONBUFFER_LINES_LAST(buf).start + CONBUFFER_LINES_LAST(buf).len;
204                 // the buffer is cyclic, so we first have two cases...
205                 if(firstline_start < lastline_onepastend) // buffer is contiguous
206                 {
207                         // put at end?
208                         if(len <= buf->text + buf->textsize - lastline_onepastend)
209                                 return lastline_onepastend;
210                         // put at beginning?
211                         else if(len <= firstline_start - buf->text)
212                                 return buf->text;
213                         else
214                                 return NULL;
215                 }
216                 else // buffer has a contiguous hole
217                 {
218                         if(len <= firstline_start - lastline_onepastend)
219                                 return lastline_onepastend;
220                         else
221                                 return NULL;
222                 }
223         }
224 }
225
226 /*
227 ================
228 ConBuffer_AddLine
229
230 Appends a given string as a new line to the console.
231 ================
232 */
233 void ConBuffer_AddLine(conbuffer_t *buf, const char *line, int len, int mask)
234 {
235         char *putpos;
236         con_lineinfo_t *p;
237
238         // developer_memory 1 during shutdown prints while conbuffer_t is being freed
239         if (!buf->active)
240                 return;
241
242         ConBuffer_FixTimes(buf);
243
244         if(len >= buf->textsize)
245         {
246                 // line too large?
247                 // only display end of line.
248                 line += len - buf->textsize + 1;
249                 len = buf->textsize - 1;
250         }
251         while(!(putpos = ConBuffer_BytesLeft(buf, len + 1)) || buf->lines_count >= buf->maxlines)
252                 ConBuffer_DeleteLine(buf);
253         memcpy(putpos, line, len);
254         putpos[len] = 0;
255         ++buf->lines_count;
256
257         //fprintf(stderr, "Now have %d lines (%d -> %d).\n", buf->lines_count, buf->lines_first, CON_LINES_LAST);
258
259         p = &CONBUFFER_LINES_LAST(buf);
260         p->start = putpos;
261         p->len = len;
262         p->addtime = cl.time;
263         p->mask = mask;
264         p->height = -1; // calculate when needed
265 }
266
267 int ConBuffer_FindPrevLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
268 {
269         int i;
270         if(start == -1)
271                 start = buf->lines_count;
272         for(i = start - 1; i >= 0; --i)
273         {
274                 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
275
276                 if((l->mask & mask_must) != mask_must)
277                         continue;
278                 if(l->mask & mask_mustnot)
279                         continue;
280
281                 return i;
282         }
283
284         return -1;
285 }
286
287 int Con_FindNextLine(conbuffer_t *buf, int mask_must, int mask_mustnot, int start)
288 {
289         int i;
290         for(i = start + 1; i < buf->lines_count; ++i)
291         {
292                 con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
293
294                 if((l->mask & mask_must) != mask_must)
295                         continue;
296                 if(l->mask & mask_mustnot)
297                         continue;
298
299                 return i;
300         }
301
302         return -1;
303 }
304
305 const char *ConBuffer_GetLine(conbuffer_t *buf, int i)
306 {
307         static char copybuf[MAX_INPUTLINE];
308         con_lineinfo_t *l = &CONBUFFER_LINES(buf, i);
309         size_t sz = l->len+1 > sizeof(copybuf) ? sizeof(copybuf) : l->len+1;
310         strlcpy(copybuf, l->start, sz);
311         return copybuf;
312 }
313
314 /*
315 ==============================================================================
316
317 LOGGING
318
319 ==============================================================================
320 */
321
322 /// \name Logging
323 //@{
324 cvar_t log_file = {0, "log_file","", "filename to log messages to"};
325 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"};
326 char log_dest_buffer[1400]; // UDP packet
327 size_t log_dest_buffer_pos;
328 unsigned int log_dest_buffer_appending;
329 char crt_log_file [MAX_OSPATH] = "";
330 qfile_t* logfile = NULL;
331
332 unsigned char* logqueue = NULL;
333 size_t logq_ind = 0;
334 size_t logq_size = 0;
335
336 void Log_ConPrint (const char *msg);
337 //@}
338 /*
339 ====================
340 Log_DestBuffer_Init
341 ====================
342 */
343 static void Log_DestBuffer_Init(void)
344 {
345         memcpy(log_dest_buffer, "\377\377\377\377n", 5); // QW rcon print
346         log_dest_buffer_pos = 5;
347 }
348
349 /*
350 ====================
351 Log_DestBuffer_Flush
352 ====================
353 */
354 void Log_DestBuffer_Flush(void)
355 {
356         lhnetaddress_t log_dest_addr;
357         lhnetsocket_t *log_dest_socket;
358         const char *s = log_dest_udp.string;
359         qboolean have_opened_temp_sockets = false;
360         if(s) if(log_dest_buffer_pos > 5)
361         {
362                 ++log_dest_buffer_appending;
363                 log_dest_buffer[log_dest_buffer_pos++] = 0;
364
365                 if(!NetConn_HaveServerPorts() && !NetConn_HaveClientPorts()) // then temporarily open one
366                 {
367                         have_opened_temp_sockets = true;
368                         NetConn_OpenServerPorts(true);
369                 }
370
371                 while(COM_ParseToken_Console(&s))
372                         if(LHNETADDRESS_FromString(&log_dest_addr, com_token, 26000))
373                         {
374                                 log_dest_socket = NetConn_ChooseClientSocketForAddress(&log_dest_addr);
375                                 if(!log_dest_socket)
376                                         log_dest_socket = NetConn_ChooseServerSocketForAddress(&log_dest_addr);
377                                 if(log_dest_socket)
378                                         NetConn_WriteString(log_dest_socket, log_dest_buffer, &log_dest_addr);
379                         }
380
381                 if(have_opened_temp_sockets)
382                         NetConn_CloseServerPorts();
383                 --log_dest_buffer_appending;
384         }
385         log_dest_buffer_pos = 0;
386 }
387
388 /*
389 ====================
390 Log_Timestamp
391 ====================
392 */
393 const char* Log_Timestamp (const char *desc)
394 {
395         static char timestamp [128];
396         time_t crt_time;
397 #if _MSC_VER >= 1400
398         struct tm crt_tm;
399 #else
400         struct tm *crt_tm;
401 #endif
402         char timestring [64];
403
404         // Build the time stamp (ex: "Wed Jun 30 21:49:08 1993");
405         time (&crt_time);
406 #if _MSC_VER >= 1400
407         localtime_s (&crt_tm, &crt_time);
408         strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", &crt_tm);
409 #else
410         crt_tm = localtime (&crt_time);
411         strftime (timestring, sizeof (timestring), "%a %b %d %H:%M:%S %Y", crt_tm);
412 #endif
413
414         if (desc != NULL)
415                 dpsnprintf (timestamp, sizeof (timestamp), "====== %s (%s) ======\n", desc, timestring);
416         else
417                 dpsnprintf (timestamp, sizeof (timestamp), "====== %s ======\n", timestring);
418
419         return timestamp;
420 }
421
422
423 /*
424 ====================
425 Log_Open
426 ====================
427 */
428 void Log_Open (void)
429 {
430         if (logfile != NULL || log_file.string[0] == '\0')
431                 return;
432
433         logfile = FS_OpenRealFile(log_file.string, "a", false);
434         if (logfile != NULL)
435         {
436                 strlcpy (crt_log_file, log_file.string, sizeof (crt_log_file));
437                 FS_Print (logfile, Log_Timestamp ("Log started"));
438         }
439 }
440
441
442 /*
443 ====================
444 Log_Close
445 ====================
446 */
447 void Log_Close (void)
448 {
449         if (logfile == NULL)
450                 return;
451
452         FS_Print (logfile, Log_Timestamp ("Log stopped"));
453         FS_Print (logfile, "\n");
454         FS_Close (logfile);
455
456         logfile = NULL;
457         crt_log_file[0] = '\0';
458 }
459
460
461 /*
462 ====================
463 Log_Start
464 ====================
465 */
466 void Log_Start (void)
467 {
468         size_t pos;
469         size_t n;
470         Log_Open ();
471
472         // Dump the contents of the log queue into the log file and free it
473         if (logqueue != NULL)
474         {
475                 unsigned char *temp = logqueue;
476                 logqueue = NULL;
477                 if(logq_ind != 0)
478                 {
479                         if (logfile != NULL)
480                                 FS_Write (logfile, temp, logq_ind);
481                         if(*log_dest_udp.string)
482                         {
483                                 for(pos = 0; pos < logq_ind; )
484                                 {
485                                         if(log_dest_buffer_pos == 0)
486                                                 Log_DestBuffer_Init();
487                                         n = min(sizeof(log_dest_buffer) - log_dest_buffer_pos - 1, logq_ind - pos);
488                                         memcpy(log_dest_buffer + log_dest_buffer_pos, temp + pos, n);
489                                         log_dest_buffer_pos += n;
490                                         Log_DestBuffer_Flush();
491                                         pos += n;
492                                 }
493                         }
494                 }
495                 Mem_Free (temp);
496                 logq_ind = 0;
497                 logq_size = 0;
498         }
499 }
500
501
502 /*
503 ================
504 Log_ConPrint
505 ================
506 */
507 void Log_ConPrint (const char *msg)
508 {
509         static qboolean inprogress = false;
510
511         // don't allow feedback loops with memory error reports
512         if (inprogress)
513                 return;
514         inprogress = true;
515
516         // Until the host is completely initialized, we maintain a log queue
517         // to store the messages, since the log can't be started before
518         if (logqueue != NULL)
519         {
520                 size_t remain = logq_size - logq_ind;
521                 size_t len = strlen (msg);
522
523                 // If we need to enlarge the log queue
524                 if (len > remain)
525                 {
526                         size_t factor = ((logq_ind + len) / logq_size) + 1;
527                         unsigned char* newqueue;
528
529                         logq_size *= factor;
530                         newqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
531                         memcpy (newqueue, logqueue, logq_ind);
532                         Mem_Free (logqueue);
533                         logqueue = newqueue;
534                         remain = logq_size - logq_ind;
535                 }
536                 memcpy (&logqueue[logq_ind], msg, len);
537                 logq_ind += len;
538
539                 inprogress = false;
540                 return;
541         }
542
543         // Check if log_file has changed
544         if (strcmp (crt_log_file, log_file.string) != 0)
545         {
546                 Log_Close ();
547                 Log_Open ();
548         }
549
550         // If a log file is available
551         if (logfile != NULL)
552                 FS_Print (logfile, msg);
553
554         inprogress = false;
555 }
556
557
558 /*
559 ================
560 Log_Printf
561 ================
562 */
563 void Log_Printf (const char *logfilename, const char *fmt, ...)
564 {
565         qfile_t *file;
566
567         file = FS_OpenRealFile(logfilename, "a", true);
568         if (file != NULL)
569         {
570                 va_list argptr;
571
572                 va_start (argptr, fmt);
573                 FS_VPrintf (file, fmt, argptr);
574                 va_end (argptr);
575
576                 FS_Close (file);
577         }
578 }
579
580
581 /*
582 ==============================================================================
583
584 CONSOLE
585
586 ==============================================================================
587 */
588
589 /*
590 ================
591 Con_ToggleConsole_f
592 ================
593 */
594 void Con_ToggleConsole_f (void)
595 {
596         // toggle the 'user wants console' bit
597         key_consoleactive ^= KEY_CONSOLEACTIVE_USER;
598         Con_ClearNotify();
599 }
600
601 /*
602 ================
603 Con_ClearNotify
604 ================
605 */
606 void Con_ClearNotify (void)
607 {
608         int i;
609         for(i = 0; i < CON_LINES_COUNT; ++i)
610                 CON_LINES(i).mask |= CON_MASK_HIDENOTIFY;
611 }
612
613
614 /*
615 ================
616 Con_MessageMode_f
617 ================
618 */
619 void Con_MessageMode_f (void)
620 {
621         key_dest = key_message;
622         chat_mode = 0; // "say"
623         chat_bufferlen = 0;
624         chat_buffer[0] = 0;
625 }
626
627
628 /*
629 ================
630 Con_MessageMode2_f
631 ================
632 */
633 void Con_MessageMode2_f (void)
634 {
635         key_dest = key_message;
636         chat_mode = 1; // "say_team"
637         chat_bufferlen = 0;
638         chat_buffer[0] = 0;
639 }
640
641 /*
642 ================
643 Con_CommandMode_f
644 ================
645 */
646 void Con_CommandMode_f (void)
647 {
648         key_dest = key_message;
649         if(Cmd_Argc() > 1)
650         {
651                 dpsnprintf(chat_buffer, sizeof(chat_buffer), "%s ", Cmd_Args());
652                 chat_bufferlen = strlen(chat_buffer);
653         }
654         chat_mode = -1; // command
655 }
656
657 /*
658 ================
659 Con_CheckResize
660 ================
661 */
662 void Con_CheckResize (void)
663 {
664         int i, width;
665         float f;
666
667         f = bound(1, con_textsize.value, 128);
668         if(f != con_textsize.value)
669                 Cvar_SetValueQuick(&con_textsize, f);
670         width = (int)floor(vid_conwidth.value / con_textsize.value);
671         width = bound(1, width, con.textsize/4);
672                 // FIXME uses con in a non abstracted way
673
674         if (width == con_linewidth)
675                 return;
676
677         con_linewidth = width;
678
679         for(i = 0; i < CON_LINES_COUNT; ++i)
680                 CON_LINES(i).height = -1; // recalculate when next needed
681
682         Con_ClearNotify();
683         con_backscroll = 0;
684 }
685
686 //[515]: the simplest command ever
687 //LordHavoc: not so simple after I made it print usage...
688 static void Con_Maps_f (void)
689 {
690         if (Cmd_Argc() > 2)
691         {
692                 Con_Printf("usage: maps [mapnameprefix]\n");
693                 return;
694         }
695         else if (Cmd_Argc() == 2)
696                 GetMapList(Cmd_Argv(1), NULL, 0);
697         else
698                 GetMapList("", NULL, 0);
699 }
700
701 void Con_ConDump_f (void)
702 {
703         int i;
704         qfile_t *file;
705         if (Cmd_Argc() != 2)
706         {
707                 Con_Printf("usage: condump <filename>\n");
708                 return;
709         }
710         file = FS_OpenRealFile(Cmd_Argv(1), "w", false);
711         if (!file)
712         {
713                 Con_Printf("condump: unable to write file \"%s\"\n", Cmd_Argv(1));
714                 return;
715         }
716         for(i = 0; i < CON_LINES_COUNT; ++i)
717         {
718                 FS_Write(file, CON_LINES(i).start, CON_LINES(i).len);
719                 FS_Write(file, "\n", 1);
720         }
721         FS_Close(file);
722 }
723
724 void Con_Clear_f (void)
725 {
726         ConBuffer_Clear(&con);
727 }
728
729 /*
730 ================
731 Con_Init
732 ================
733 */
734 void Con_Init (void)
735 {
736         con_linewidth = 80;
737         ConBuffer_Init(&con, CON_TEXTSIZE, CON_MAXLINES, zonemempool);
738
739         // Allocate a log queue, this will be freed after configs are parsed
740         logq_size = MAX_INPUTLINE;
741         logqueue = (unsigned char *)Mem_Alloc (tempmempool, logq_size);
742         logq_ind = 0;
743
744         Cvar_RegisterVariable (&sys_colortranslation);
745         Cvar_RegisterVariable (&sys_specialcharactertranslation);
746
747         Cvar_RegisterVariable (&log_file);
748         Cvar_RegisterVariable (&log_dest_udp);
749
750         // support for the classic Quake option
751 // COMMANDLINEOPTION: Console: -condebug logs console messages to qconsole.log, see also log_file
752         if (COM_CheckParm ("-condebug") != 0)
753                 Cvar_SetQuick (&log_file, "qconsole.log");
754
755         // register our cvars
756         Cvar_RegisterVariable (&con_chat);
757         Cvar_RegisterVariable (&con_chatpos);
758         Cvar_RegisterVariable (&con_chatrect_x);
759         Cvar_RegisterVariable (&con_chatrect_y);
760         Cvar_RegisterVariable (&con_chatrect);
761         Cvar_RegisterVariable (&con_chatsize);
762         Cvar_RegisterVariable (&con_chattime);
763         Cvar_RegisterVariable (&con_chatwidth);
764         Cvar_RegisterVariable (&con_notify);
765         Cvar_RegisterVariable (&con_notifyalign);
766         Cvar_RegisterVariable (&con_notifysize);
767         Cvar_RegisterVariable (&con_notifytime);
768         Cvar_RegisterVariable (&con_textsize);
769         Cvar_RegisterVariable (&con_chatsound);
770
771         // --blub
772         Cvar_RegisterVariable (&con_nickcompletion);
773         Cvar_RegisterVariable (&con_nickcompletion_flags);
774
775         Cvar_RegisterVariable (&con_completion_playdemo); // *.dem
776         Cvar_RegisterVariable (&con_completion_timedemo); // *.dem
777         Cvar_RegisterVariable (&con_completion_exec); // *.cfg
778
779         // register our commands
780         Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f, "opens or closes the console");
781         Cmd_AddCommand ("messagemode", Con_MessageMode_f, "input a chat message to say to everyone");
782         Cmd_AddCommand ("messagemode2", Con_MessageMode2_f, "input a chat message to say to only your team");
783         Cmd_AddCommand ("commandmode", Con_CommandMode_f, "input a console command");
784         Cmd_AddCommand ("clear", Con_Clear_f, "clear console history");
785         Cmd_AddCommand ("maps", Con_Maps_f, "list information about available maps");
786         Cmd_AddCommand ("condump", Con_ConDump_f, "output console history to a file (see also log_file)");
787
788         con_initialized = true;
789         Con_DPrint("Console initialized.\n");
790 }
791
792 void Con_Shutdown (void)
793 {
794         ConBuffer_Shutdown(&con);
795 }
796
797 /*
798 ================
799 Con_PrintToHistory
800
801 Handles cursor positioning, line wrapping, etc
802 All console printing must go through this in order to be displayed
803 If no console is visible, the notify window will pop up.
804 ================
805 */
806 void Con_PrintToHistory(const char *txt, int mask)
807 {
808         // process:
809         //   \n goes to next line
810         //   \r deletes current line and makes a new one
811
812         static int cr_pending = 0;
813         static char buf[CON_TEXTSIZE];
814         static int bufpos = 0;
815
816         if(!con.text) // FIXME uses a non-abstracted property of con
817                 return;
818
819         for(; *txt; ++txt)
820         {
821                 if(cr_pending)
822                 {
823                         ConBuffer_DeleteLastLine(&con);
824                         cr_pending = 0;
825                 }
826                 switch(*txt)
827                 {
828                         case 0:
829                                 break;
830                         case '\r':
831                                 ConBuffer_AddLine(&con, buf, bufpos, mask);
832                                 bufpos = 0;
833                                 cr_pending = 1;
834                                 break;
835                         case '\n':
836                                 ConBuffer_AddLine(&con, buf, bufpos, mask);
837                                 bufpos = 0;
838                                 break;
839                         default:
840                                 buf[bufpos++] = *txt;
841                                 if(bufpos >= con.textsize - 1) // FIXME uses a non-abstracted property of con
842                                 {
843                                         ConBuffer_AddLine(&con, buf, bufpos, mask);
844                                         bufpos = 0;
845                                 }
846                                 break;
847                 }
848         }
849 }
850
851 /*! The translation table between the graphical font and plain ASCII  --KB */
852 static char qfont_table[256] = {
853         '\0', '#',  '#',  '#',  '#',  '.',  '#',  '#',
854         '#',  9,    10,   '#',  ' ',  13,   '.',  '.',
855         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
856         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
857         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
858         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
859         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
860         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
861         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
862         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
863         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
864         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
865         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
866         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
867         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
868         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<',
869
870         '<',  '=',  '>',  '#',  '#',  '.',  '#',  '#',
871         '#',  '#',  ' ',  '#',  ' ',  '>',  '.',  '.',
872         '[',  ']',  '0',  '1',  '2',  '3',  '4',  '5',
873         '6',  '7',  '8',  '9',  '.',  '<',  '=',  '>',
874         ' ',  '!',  '"',  '#',  '$',  '%',  '&',  '\'',
875         '(',  ')',  '*',  '+',  ',',  '-',  '.',  '/',
876         '0',  '1',  '2',  '3',  '4',  '5',  '6',  '7',
877         '8',  '9',  ':',  ';',  '<',  '=',  '>',  '?',
878         '@',  'A',  'B',  'C',  'D',  'E',  'F',  'G',
879         'H',  'I',  'J',  'K',  'L',  'M',  'N',  'O',
880         'P',  'Q',  'R',  'S',  'T',  'U',  'V',  'W',
881         'X',  'Y',  'Z',  '[',  '\\', ']',  '^',  '_',
882         '`',  'a',  'b',  'c',  'd',  'e',  'f',  'g',
883         'h',  'i',  'j',  'k',  'l',  'm',  'n',  'o',
884         'p',  'q',  'r',  's',  't',  'u',  'v',  'w',
885         'x',  'y',  'z',  '{',  '|',  '}',  '~',  '<'
886 };
887
888 void Con_Rcon_Redirect_Init(lhnetsocket_t *sock, lhnetaddress_t *dest)
889 {
890         rcon_redirect_sock = sock;
891         rcon_redirect_dest = dest;
892         memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
893         rcon_redirect_bufferpos = 5;
894 }
895
896 void Con_Rcon_Redirect_Flush(void)
897 {
898         rcon_redirect_buffer[rcon_redirect_bufferpos] = 0;
899         NetConn_WriteString(rcon_redirect_sock, rcon_redirect_buffer, rcon_redirect_dest);
900         memcpy(rcon_redirect_buffer, "\377\377\377\377n", 5); // QW rcon print
901         rcon_redirect_bufferpos = 5;
902 }
903
904 void Con_Rcon_Redirect_End(void)
905 {
906         Con_Rcon_Redirect_Flush();
907         rcon_redirect_dest = NULL;
908         rcon_redirect_sock = NULL;
909 }
910
911 void Con_Rcon_Redirect_Abort(void)
912 {
913         rcon_redirect_dest = NULL;
914         rcon_redirect_sock = NULL;
915 }
916
917 /*
918 ================
919 Con_Rcon_AddChar
920 ================
921 */
922 /// Adds a character to the rcon buffer.
923 void Con_Rcon_AddChar(int c)
924 {
925         if(log_dest_buffer_appending)
926                 return;
927         ++log_dest_buffer_appending;
928
929         // if this print is in response to an rcon command, add the character
930         // to the rcon redirect buffer
931
932         if (rcon_redirect_dest)
933         {
934                 rcon_redirect_buffer[rcon_redirect_bufferpos++] = c;
935                 if(rcon_redirect_bufferpos >= (int)sizeof(rcon_redirect_buffer) - 1)
936                         Con_Rcon_Redirect_Flush();
937         }
938         else if(*log_dest_udp.string) // don't duplicate rcon command responses here, these are sent another way
939         {
940                 if(log_dest_buffer_pos == 0)
941                         Log_DestBuffer_Init();
942                 log_dest_buffer[log_dest_buffer_pos++] = c;
943                 if(log_dest_buffer_pos >= sizeof(log_dest_buffer) - 1) // minus one, to allow for terminating zero
944                         Log_DestBuffer_Flush();
945         }
946         else
947                 log_dest_buffer_pos = 0;
948
949         --log_dest_buffer_appending;
950 }
951
952 /**
953  * Convert an RGB color to its nearest quake color.
954  * I'll cheat on this a bit by translating the colors to HSV first,
955  * S and V decide if it's black or white, otherwise, H will decide the
956  * actual color.
957  * @param _r Red (0-255)
958  * @param _g Green (0-255)
959  * @param _b Blue (0-255)
960  * @return A quake color character.
961  */
962 static char Sys_Con_NearestColor(const unsigned char _r, const unsigned char _g, const unsigned char _b)
963 {
964         float r = ((float)_r)/255.0;
965         float g = ((float)_g)/255.0;
966         float b = ((float)_b)/255.0;
967         float min = min(r, min(g, b));
968         float max = max(r, max(g, b));
969
970         int h; ///< Hue angle [0,360]
971         float s; ///< Saturation [0,1]
972         float v = max; ///< In HSV v == max [0,1]
973
974         if(max == min)
975                 s = 0;
976         else
977                 s = 1.0 - (min/max);
978
979         // Saturation threshold. We now say 0.2 is the minimum value for a color!
980         if(s < 0.2)
981         {
982                 // If the value is less than half, return a black color code.
983                 // Otherwise return a white one.
984                 if(v < 0.5)
985                         return '0';
986                 return '7';
987         }
988
989         // Let's get the hue angle to define some colors:
990         if(max == min)
991                 h = 0;
992         else if(max == r)
993                 h = (int)(60.0 * (g-b)/(max-min))%360;
994         else if(max == g)
995                 h = (int)(60.0 * (b-r)/(max-min) + 120);
996         else // if(max == b) redundant check
997                 h = (int)(60.0 * (r-g)/(max-min) + 240);
998
999         if(h < 36) // *red* to orange
1000                 return '1';
1001         else if(h < 80) // orange over *yellow* to evilish-bright-green
1002                 return '3';
1003         else if(h < 150) // evilish-bright-green over *green* to ugly bright blue
1004                 return '2';
1005         else if(h < 200) // ugly bright blue over *bright blue* to darkish blue
1006                 return '5';
1007         else if(h < 270) // darkish blue over *dark blue* to cool purple
1008                 return '4';
1009         else if(h < 330) // cool purple over *purple* to ugly swiny red
1010                 return '6';
1011         else // ugly red to red closes the circly
1012                 return '1';
1013 }
1014
1015 /*
1016 ================
1017 Con_MaskPrint
1018 ================
1019 */
1020 extern cvar_t timestamps;
1021 extern cvar_t timeformat;
1022 extern qboolean sys_nostdout;
1023 void Con_MaskPrint(int additionalmask, const char *msg)
1024 {
1025         static int mask = 0;
1026         static int index = 0;
1027         static char line[MAX_INPUTLINE];
1028
1029         for (;*msg;msg++)
1030         {
1031                 Con_Rcon_AddChar(*msg);
1032                 if (index == 0)
1033                         mask |= additionalmask;
1034                 // if this is the beginning of a new line, print timestamp
1035                 if (index == 0)
1036                 {
1037                         const char *timestamp = timestamps.integer ? Sys_TimeString(timeformat.string) : "";
1038                         // reset the color
1039                         // FIXME: 1. perhaps we should use a terminal system 2. use a constant instead of 7!
1040                         line[index++] = STRING_COLOR_TAG;
1041                         // assert( STRING_COLOR_DEFAULT < 10 )
1042                         line[index++] = STRING_COLOR_DEFAULT + '0';
1043                         // special color codes for chat messages must always come first
1044                         // for Con_PrintToHistory to work properly
1045                         if (*msg == 1 || *msg == 2)
1046                         {
1047                                 // play talk wav
1048                                 if (*msg == 1)
1049                                 {
1050                                         if (con_chatsound.value)
1051                                         {
1052                                                 if(gamemode == GAME_NEXUIZ || gamemode == GAME_XONOTIC)
1053                                                 {
1054                                                         if(msg[1] == '\r' && cl.foundtalk2wav)
1055                                                                 S_LocalSound ("sound/misc/talk2.wav");
1056                                                         else
1057                                                                 S_LocalSound ("sound/misc/talk.wav");
1058                                                 }
1059                                                 else
1060                                                 {
1061                                                         if (msg[1] == '(' && cl.foundtalk2wav)
1062                                                                 S_LocalSound ("sound/misc/talk2.wav");
1063                                                         else
1064                                                                 S_LocalSound ("sound/misc/talk.wav");
1065                                                 }
1066                                         }
1067                                         mask = CON_MASK_CHAT;
1068                                 }
1069                                 line[index++] = STRING_COLOR_TAG;
1070                                 line[index++] = '3';
1071                                 msg++;
1072                                 Con_Rcon_AddChar(*msg);
1073                         }
1074                         // store timestamp
1075                         for (;*timestamp;index++, timestamp++)
1076                                 if (index < (int)sizeof(line) - 2)
1077                                         line[index] = *timestamp;
1078                 }
1079                 // append the character
1080                 line[index++] = *msg;
1081                 // if this is a newline character, we have a complete line to print
1082                 if (*msg == '\n' || index >= (int)sizeof(line) / 2)
1083                 {
1084                         // terminate the line
1085                         line[index] = 0;
1086                         // send to log file
1087                         Log_ConPrint(line);
1088                         // send to scrollable buffer
1089                         if (con_initialized && cls.state != ca_dedicated)
1090                         {
1091                                 Con_PrintToHistory(line, mask);
1092                                 mask = 0;
1093                         }
1094                         // send to terminal or dedicated server window
1095                         if (!sys_nostdout)
1096                         {
1097                                 unsigned char *p;
1098                                 if(sys_specialcharactertranslation.integer)
1099                                 {
1100                                         for (p = (unsigned char *) line;*p; p++)
1101                                                 *p = qfont_table[*p];
1102                                 }
1103
1104                                 if(sys_colortranslation.integer == 1) // ANSI
1105                                 {
1106                                         static char printline[MAX_INPUTLINE * 4 + 3];
1107                                                 // 2 can become 7 bytes, rounding that up to 8, and 3 bytes are added at the end
1108                                                 // a newline can transform into four bytes, but then prevents the three extra bytes from appearing
1109                                         int lastcolor = 0;
1110                                         const char *in;
1111                                         char *out;
1112                                         int color;
1113                                         for(in = line, out = printline; *in; ++in)
1114                                         {
1115                                                 switch(*in)
1116                                                 {
1117                                                         case STRING_COLOR_TAG:
1118                                                                 if( in[1] == STRING_COLOR_RGB_TAG_CHAR && isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1119                                                                 {
1120                                                                         char r = tolower(in[2]);
1121                                                                         char g = tolower(in[3]);
1122                                                                         char b = tolower(in[4]);
1123                                                                         // it's a hex digit already, so the else part needs no check --blub
1124                                                                         if(isdigit(r)) r -= '0';
1125                                                                         else r -= 87;
1126                                                                         if(isdigit(g)) g -= '0';
1127                                                                         else g -= 87;
1128                                                                         if(isdigit(b)) b -= '0';
1129                                                                         else b -= 87;
1130                                                                         
1131                                                                         color = Sys_Con_NearestColor(r * 17, g * 17, b * 17);
1132                                                                         in += 3; // 3 only, the switch down there does the fourth
1133                                                                 }
1134                                                                 else
1135                                                                         color = in[1];
1136                                                                 
1137                                                                 switch(color)
1138                                                                 {
1139                                                                         case STRING_COLOR_TAG:
1140                                                                                 ++in;
1141                                                                                 *out++ = STRING_COLOR_TAG;
1142                                                                                 break;
1143                                                                         case '0':
1144                                                                         case '7':
1145                                                                                 // normal color
1146                                                                                 ++in;
1147                                                                                 if(lastcolor == 0) break; else lastcolor = 0;
1148                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1149                                                                                 break;
1150                                                                         case '1':
1151                                                                                 // light red
1152                                                                                 ++in;
1153                                                                                 if(lastcolor == 1) break; else lastcolor = 1;
1154                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '1'; *out++ = 'm';
1155                                                                                 break;
1156                                                                         case '2':
1157                                                                                 // light green
1158                                                                                 ++in;
1159                                                                                 if(lastcolor == 2) break; else lastcolor = 2;
1160                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '2'; *out++ = 'm';
1161                                                                                 break;
1162                                                                         case '3':
1163                                                                                 // yellow
1164                                                                                 ++in;
1165                                                                                 if(lastcolor == 3) break; else lastcolor = 3;
1166                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '3'; *out++ = 'm';
1167                                                                                 break;
1168                                                                         case '4':
1169                                                                                 // light blue
1170                                                                                 ++in;
1171                                                                                 if(lastcolor == 4) break; else lastcolor = 4;
1172                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '4'; *out++ = 'm';
1173                                                                                 break;
1174                                                                         case '5':
1175                                                                                 // light cyan
1176                                                                                 ++in;
1177                                                                                 if(lastcolor == 5) break; else lastcolor = 5;
1178                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '6'; *out++ = 'm';
1179                                                                                 break;
1180                                                                         case '6':
1181                                                                                 // light magenta
1182                                                                                 ++in;
1183                                                                                 if(lastcolor == 6) break; else lastcolor = 6;
1184                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '1'; *out++ = ';'; *out++ = '3'; *out++ = '5'; *out++ = 'm';
1185                                                                                 break;
1186                                                                         // 7 handled above
1187                                                                         case '8':
1188                                                                         case '9':
1189                                                                                 // bold normal color
1190                                                                                 ++in;
1191                                                                                 if(lastcolor == 8) break; else lastcolor = 8;
1192                                                                                 *out++ = 0x1B; *out++ = '['; *out++ = '0'; *out++ = ';'; *out++ = '1'; *out++ = 'm';
1193                                                                                 break;
1194                                                                         default:
1195                                                                                 *out++ = STRING_COLOR_TAG;
1196                                                                                 break;
1197                                                                 }
1198                                                                 break;
1199                                                         case '\n':
1200                                                                 if(lastcolor != 0)
1201                                                                 {
1202                                                                         *out++ = 0x1B; *out++ = '['; *out++ = 'm';
1203                                                                         lastcolor = 0;
1204                                                                 }
1205                                                                 *out++ = *in;
1206                                                                 break;
1207                                                         default:
1208                                                                 *out++ = *in;
1209                                                                 break;
1210                                                 }
1211                                         }
1212                                         if(lastcolor != 0)
1213                                         {
1214                                                 *out++ = 0x1B;
1215                                                 *out++ = '[';
1216                                                 *out++ = 'm';
1217                                         }
1218                                         *out++ = 0;
1219                                         Sys_PrintToTerminal(printline);
1220                                 }
1221                                 else if(sys_colortranslation.integer == 2) // Quake
1222                                 {
1223                                         Sys_PrintToTerminal(line);
1224                                 }
1225                                 else // strip
1226                                 {
1227                                         static char printline[MAX_INPUTLINE]; // it can only get shorter here
1228                                         const char *in;
1229                                         char *out;
1230                                         for(in = line, out = printline; *in; ++in)
1231                                         {
1232                                                 switch(*in)
1233                                                 {
1234                                                         case STRING_COLOR_TAG:
1235                                                                 switch(in[1])
1236                                                                 {
1237                                                                         case STRING_COLOR_RGB_TAG_CHAR:
1238                                                                                 if ( isxdigit(in[2]) && isxdigit(in[3]) && isxdigit(in[4]) )
1239                                                                                 {
1240                                                                                         in+=4;
1241                                                                                         break;
1242                                                                                 }
1243                                                                                 *out++ = STRING_COLOR_TAG;
1244                                                                                 *out++ = STRING_COLOR_RGB_TAG_CHAR;
1245                                                                                 ++in;
1246                                                                                 break;
1247                                                                         case STRING_COLOR_TAG:
1248                                                                                 ++in;
1249                                                                                 *out++ = STRING_COLOR_TAG;
1250                                                                                 break;
1251                                                                         case '0':
1252                                                                         case '1':
1253                                                                         case '2':
1254                                                                         case '3':
1255                                                                         case '4':
1256                                                                         case '5':
1257                                                                         case '6':
1258                                                                         case '7':
1259                                                                         case '8':
1260                                                                         case '9':
1261                                                                                 ++in;
1262                                                                                 break;
1263                                                                         default:
1264                                                                                 *out++ = STRING_COLOR_TAG;
1265                                                                                 break;
1266                                                                 }
1267                                                                 break;
1268                                                         default:
1269                                                                 *out++ = *in;
1270                                                                 break;
1271                                                 }
1272                                         }
1273                                         *out++ = 0;
1274                                         Sys_PrintToTerminal(printline);
1275                                 }
1276                         }
1277                         // empty the line buffer
1278                         index = 0;
1279                 }
1280         }
1281 }
1282
1283 /*
1284 ================
1285 Con_MaskPrintf
1286 ================
1287 */
1288 void Con_MaskPrintf(int mask, const char *fmt, ...)
1289 {
1290         va_list argptr;
1291         char msg[MAX_INPUTLINE];
1292
1293         va_start(argptr,fmt);
1294         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1295         va_end(argptr);
1296
1297         Con_MaskPrint(mask, msg);
1298 }
1299
1300 /*
1301 ================
1302 Con_Print
1303 ================
1304 */
1305 void Con_Print(const char *msg)
1306 {
1307         Con_MaskPrint(CON_MASK_PRINT, msg);
1308 }
1309
1310 /*
1311 ================
1312 Con_Printf
1313 ================
1314 */
1315 void Con_Printf(const char *fmt, ...)
1316 {
1317         va_list argptr;
1318         char msg[MAX_INPUTLINE];
1319
1320         va_start(argptr,fmt);
1321         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1322         va_end(argptr);
1323
1324         Con_MaskPrint(CON_MASK_PRINT, msg);
1325 }
1326
1327 /*
1328 ================
1329 Con_DPrint
1330 ================
1331 */
1332 void Con_DPrint(const char *msg)
1333 {
1334         if(developer.integer < 0) // at 0, we still add to the buffer but hide
1335                 return;
1336
1337         Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1338 }
1339
1340 /*
1341 ================
1342 Con_DPrintf
1343 ================
1344 */
1345 void Con_DPrintf(const char *fmt, ...)
1346 {
1347         va_list argptr;
1348         char msg[MAX_INPUTLINE];
1349
1350         if(developer.integer < 0) // at 0, we still add to the buffer but hide
1351                 return;
1352
1353         va_start(argptr,fmt);
1354         dpvsnprintf(msg,sizeof(msg),fmt,argptr);
1355         va_end(argptr);
1356
1357         Con_MaskPrint(CON_MASK_DEVELOPER, msg);
1358 }
1359
1360
1361 /*
1362 ==============================================================================
1363
1364 DRAWING
1365
1366 ==============================================================================
1367 */
1368
1369 /*
1370 ================
1371 Con_DrawInput
1372
1373 The input line scrolls horizontally if typing goes beyond the right edge
1374
1375 Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1376 ================
1377 */
1378 extern cvar_t r_font_disable_freetype;
1379 void Con_DrawInput (void)
1380 {
1381         int             y;
1382         int             i;
1383         char editlinecopy[MAX_INPUTLINE+1], *text;
1384         float x, xo;
1385         size_t len_out;
1386         int col_out;
1387
1388         if (!key_consoleactive)
1389                 return;         // don't draw anything
1390
1391         strlcpy(editlinecopy, key_line, sizeof(editlinecopy));
1392         text = editlinecopy;
1393
1394         // Advanced Console Editing by Radix radix@planetquake.com
1395         // Added/Modified by EvilTypeGuy eviltypeguy@qeradiant.com
1396         // use strlen of edit_line instead of key_linepos to allow editing
1397         // of early characters w/o erasing
1398
1399         y = (int)strlen(text);
1400
1401         // append enoug nul-bytes to cover the utf8-versions of the cursor too
1402         for (i = y; i < y + 4 && i < (int)sizeof(editlinecopy); ++i)
1403                 text[i] = 0;
1404
1405         // add the cursor frame
1406         if (r_font_disable_freetype.integer)
1407         {
1408                 // this code is freetype incompatible!
1409                 if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
1410                 {
1411                         if (!utf8_enable.integer)
1412                                 text[key_linepos] = 11 + 130 * key_insert;      // either solid or triangle facing right
1413                         else if (y + 3 < (int)sizeof(editlinecopy)-1)
1414                         {
1415                                 int ofs = u8_bytelen(text + key_linepos, 1);
1416                                 size_t len;
1417                                 const char *curbuf;
1418                                 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1419
1420                                 if (curbuf)
1421                                 {
1422                                         memmove(text + key_linepos + len, text + key_linepos + ofs, sizeof(editlinecopy) - key_linepos - len);
1423                                         memcpy(text + key_linepos, curbuf, len);
1424                                 }
1425                         } else
1426                                 text[key_linepos] = '-' + ('+' - '-') * key_insert;
1427                 }
1428         }
1429
1430 //      text[key_linepos + 1] = 0;
1431
1432         len_out = key_linepos;
1433         col_out = -1;
1434         xo = DrawQ_TextWidth_UntilWidth_TrackColors(text, &len_out, con_textsize.value, con_textsize.value, &col_out, false, FONT_CONSOLE, 1000000000);
1435         x = vid_conwidth.value * 0.95 - xo; // scroll
1436         if(x >= 0)
1437                 x = 0;
1438
1439         // draw it
1440         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 );
1441
1442         // add a cursor on top of this (when using freetype)
1443         if (!r_font_disable_freetype.integer)
1444         {
1445                 if ((int)(realtime*con_cursorspeed) & 1)                // cursor is visible
1446                 {
1447                         if (!utf8_enable.integer)
1448                         {
1449                                 text[0] = 11 + 130 * key_insert;        // either solid or triangle facing right
1450                                 text[1] = 0;
1451                         }
1452                         else
1453                         {
1454                                 size_t len;
1455                                 const char *curbuf;
1456                                 curbuf = u8_encodech(0xE000 + 11 + 130 * key_insert, &len);
1457                                 memcpy(text, curbuf, len);
1458                                 text[len] = 0;
1459                         }
1460                         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);
1461                 }
1462         }
1463
1464         // remove cursor
1465 //      key_line[key_linepos] = 0;
1466 }
1467
1468 typedef struct
1469 {
1470         dp_font_t *font;
1471         float alignment; // 0 = left, 0.5 = center, 1 = right
1472         float fontsize;
1473         float x;
1474         float y;
1475         float width;
1476         float ymin, ymax;
1477         const char *continuationString;
1478
1479         // PRIVATE:
1480         int colorindex; // init to -1
1481 }
1482 con_text_info_t;
1483
1484 float Con_WordWidthFunc(void *passthrough, const char *w, size_t *length, float maxWidth)
1485 {
1486         con_text_info_t *ti = (con_text_info_t *) passthrough;
1487         if(w == NULL)
1488         {
1489                 ti->colorindex = -1;
1490                 return ti->fontsize * ti->font->maxwidth;
1491         }
1492         if(maxWidth >= 0)
1493                 return DrawQ_TextWidth_UntilWidth(w, length, ti->fontsize, ti->fontsize, false, ti->font, -maxWidth); // -maxWidth: we want at least one char
1494         else if(maxWidth == -1)
1495                 return DrawQ_TextWidth(w, *length, ti->fontsize, ti->fontsize, false, ti->font);
1496         else
1497         {
1498                 printf("Con_WordWidthFunc: can't get here (maxWidth should never be %f)\n", maxWidth);
1499                 // Note: this is NOT a Con_Printf, as it could print recursively
1500                 return 0;
1501         }
1502 }
1503
1504 int Con_CountLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1505 {
1506         (void) passthrough;
1507         (void) line;
1508         (void) length;
1509         (void) width;
1510         (void) isContinuation;
1511         return 1;
1512 }
1513
1514 int Con_DisplayLineFunc(void *passthrough, const char *line, size_t length, float width, qboolean isContinuation)
1515 {
1516         con_text_info_t *ti = (con_text_info_t *) passthrough;
1517
1518         if(ti->y < ti->ymin - 0.001)
1519                 (void) 0;
1520         else if(ti->y > ti->ymax - ti->fontsize + 0.001)
1521                 (void) 0;
1522         else
1523         {
1524                 int x = (int) (ti->x + (ti->width - width) * ti->alignment);
1525                 if(isContinuation && *ti->continuationString)
1526                         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);
1527                 if(length > 0)
1528                         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);
1529         }
1530
1531         ti->y += ti->fontsize;
1532         return 1;
1533 }
1534
1535 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)
1536 {
1537         int i;
1538         int lines = 0;
1539         int maxlines = (int) floor(height / fontsize + 0.01f);
1540         int startidx;
1541         int nskip = 0;
1542         int continuationWidth = 0;
1543         size_t l;
1544         double t = cl.time; // saved so it won't change
1545         con_text_info_t ti;
1546
1547         ti.font = (mask_must & CON_MASK_CHAT) ? FONT_CHAT : FONT_NOTIFY;
1548         ti.fontsize = fontsize;
1549         ti.alignment = alignment_x;
1550         ti.width = width;
1551         ti.ymin = y;
1552         ti.ymax = y + height;
1553         ti.continuationString = continuationString;
1554
1555         l = 0;
1556         Con_WordWidthFunc(&ti, NULL, &l, -1);
1557         l = strlen(continuationString);
1558         continuationWidth = (int) Con_WordWidthFunc(&ti, continuationString, &l, -1);
1559
1560         // first find the first line to draw by backwards iterating and word wrapping to find their length...
1561         startidx = CON_LINES_COUNT;
1562         for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1563         {
1564                 con_lineinfo_t *l = &CON_LINES(i);
1565                 int mylines;
1566
1567                 if((l->mask & mask_must) != mask_must)
1568                         continue;
1569                 if(l->mask & mask_mustnot)
1570                         continue;
1571                 if(maxage && (l->addtime < t - maxage))
1572                         continue;
1573
1574                 // WE FOUND ONE!
1575                 // Calculate its actual height...
1576                 mylines = COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, &ti);
1577                 if(lines + mylines >= maxlines)
1578                 {
1579                         nskip = lines + mylines - maxlines;
1580                         lines = maxlines;
1581                         startidx = i;
1582                         break;
1583                 }
1584                 lines += mylines;
1585                 startidx = i;
1586         }
1587
1588         // then center according to the calculated amount of lines...
1589         ti.x = x;
1590         ti.y = y + alignment_y * (height - lines * fontsize) - nskip * fontsize;
1591
1592         // then actually draw
1593         for(i = startidx; i < CON_LINES_COUNT; ++i)
1594         {
1595                 con_lineinfo_t *l = &CON_LINES(i);
1596
1597                 if((l->mask & mask_must) != mask_must)
1598                         continue;
1599                 if(l->mask & mask_mustnot)
1600                         continue;
1601                 if(maxage && (l->addtime < t - maxage))
1602                         continue;
1603
1604                 COM_Wordwrap(l->start, l->len, continuationWidth, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1605         }
1606
1607         return lines;
1608 }
1609
1610 /*
1611 ================
1612 Con_DrawNotify
1613
1614 Draws the last few lines of output transparently over the game top
1615 ================
1616 */
1617 void Con_DrawNotify (void)
1618 {
1619         float   x, v, xr;
1620         float chatstart, notifystart, inputsize, height;
1621         float align;
1622         char    temptext[MAX_INPUTLINE];
1623         int numChatlines;
1624         int chatpos;
1625
1626         ConBuffer_FixTimes(&con);
1627
1628         numChatlines = con_chat.integer;
1629
1630         chatpos = con_chatpos.integer;
1631
1632         if (con_notify.integer < 0)
1633                 Cvar_SetValueQuick(&con_notify, 0);
1634         if (gamemode == GAME_TRANSFUSION)
1635                 v = 8; // vertical offset
1636         else
1637                 v = 0;
1638
1639         // GAME_NEXUIZ: center, otherwise left justify
1640         align = con_notifyalign.value;
1641         if(!*con_notifyalign.string) // empty string, evaluated to 0 above
1642         {
1643                 if(gamemode == GAME_NEXUIZ)
1644                         align = 0.5;
1645         }
1646
1647         if(numChatlines || !con_chatrect.integer)
1648         {
1649                 if(chatpos == 0)
1650                 {
1651                         // first chat, input line, then notify
1652                         chatstart = v;
1653                         notifystart = v + (numChatlines + 1) * con_chatsize.value;
1654                 }
1655                 else if(chatpos > 0)
1656                 {
1657                         // first notify, then (chatpos-1) empty lines, then chat, then input
1658                         notifystart = v;
1659                         chatstart = v + (con_notify.value + (chatpos - 1)) * con_notifysize.value;
1660                 }
1661                 else // if(chatpos < 0)
1662                 {
1663                         // first notify, then much space, then chat, then input, then -chatpos-1 empty lines
1664                         notifystart = v;
1665                         chatstart = vid_conheight.value - (-chatpos-1 + numChatlines + 1) * con_chatsize.value;
1666                 }
1667         }
1668         else
1669         {
1670                 // just notify and input
1671                 notifystart = v;
1672                 chatstart = 0; // shut off gcc warning
1673         }
1674
1675         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, "");
1676
1677         if(con_chatrect.integer)
1678         {
1679                 x = con_chatrect_x.value * vid_conwidth.value;
1680                 v = con_chatrect_y.value * vid_conheight.value;
1681         }
1682         else
1683         {
1684                 x = 0;
1685                 if(numChatlines) // only do this if chat area is enabled, or this would move the input line wrong
1686                         v = chatstart;
1687         }
1688         height = numChatlines * con_chatsize.value;
1689
1690         if(numChatlines)
1691         {
1692                 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
1693                 v += height;
1694         }
1695         if (key_dest == key_message)
1696         {
1697                 //static char *cursor[2] = { "\xee\x80\x8a", "\xee\x80\x8b" }; // { off, on }
1698                 int colorindex = -1;
1699                 const char *cursor;
1700                 cursor = u8_encodech(0xE00A + ((int)(realtime * con_cursorspeed)&1), NULL);
1701
1702                 // LordHavoc: speedup, and other improvements
1703                 if (chat_mode < 0)
1704                         dpsnprintf(temptext, sizeof(temptext), "]%s%s", chat_buffer, cursor);
1705                 else if(chat_mode)
1706                         dpsnprintf(temptext, sizeof(temptext), "say_team:%s%s", chat_buffer, cursor);
1707                 else
1708                         dpsnprintf(temptext, sizeof(temptext), "say:%s%s", chat_buffer, cursor);
1709
1710                 // FIXME word wrap
1711                 inputsize = (numChatlines ? con_chatsize : con_notifysize).value;
1712                 xr = vid_conwidth.value - DrawQ_TextWidth(temptext, 0, inputsize, inputsize, false, FONT_CHAT);
1713                 x = min(xr, x);
1714                 DrawQ_String(x, v, temptext, 0, inputsize, inputsize, 1.0, 1.0, 1.0, 1.0, 0, &colorindex, false, FONT_CHAT);
1715         }
1716 }
1717
1718 /*
1719 ================
1720 Con_LineHeight
1721
1722 Returns the height of a given console line; calculates it if necessary.
1723 ================
1724 */
1725 int Con_LineHeight(int lineno)
1726 {
1727         con_lineinfo_t *li = &CON_LINES(lineno);
1728         if(li->height == -1)
1729         {
1730                 float width = vid_conwidth.value;
1731                 con_text_info_t ti;
1732                 con_lineinfo_t *li = &CON_LINES(lineno);
1733                 ti.fontsize = con_textsize.value;
1734                 ti.font = FONT_CONSOLE;
1735                 li->height = COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_CountLineFunc, NULL);
1736         }
1737         return li->height;
1738 }
1739
1740 /*
1741 ================
1742 Con_DrawConsoleLine
1743
1744 Draws a line of the console; returns its height in lines.
1745 If alpha is 0, the line is not drawn, but still wrapped and its height
1746 returned.
1747 ================
1748 */
1749 int Con_DrawConsoleLine(int mask_must, int mask_mustnot, float y, int lineno, float ymin, float ymax)
1750 {
1751         float width = vid_conwidth.value;
1752         con_text_info_t ti;
1753         con_lineinfo_t *li = &CON_LINES(lineno);
1754
1755         if((li->mask & mask_must) != mask_must)
1756                 return 0;
1757         if((li->mask & mask_mustnot) != 0)
1758                 return 0;
1759
1760         ti.continuationString = "";
1761         ti.alignment = 0;
1762         ti.fontsize = con_textsize.value;
1763         ti.font = FONT_CONSOLE;
1764         ti.x = 0;
1765         ti.y = y - (Con_LineHeight(lineno) - 1) * ti.fontsize;
1766         ti.ymin = ymin;
1767         ti.ymax = ymax;
1768         ti.width = width;
1769
1770         return COM_Wordwrap(li->start, li->len, 0, width, Con_WordWidthFunc, &ti, Con_DisplayLineFunc, &ti);
1771 }
1772
1773 /*
1774 ================
1775 Con_LastVisibleLine
1776
1777 Calculates the last visible line index and how much to show of it based on
1778 con_backscroll.
1779 ================
1780 */
1781 static void Con_LastVisibleLine(int mask_must, int mask_mustnot, int *last, int *limitlast)
1782 {
1783         int lines_seen = 0;
1784         int i;
1785
1786         if(con_backscroll < 0)
1787                 con_backscroll = 0;
1788
1789         *last = 0;
1790
1791         // now count until we saw con_backscroll actual lines
1792         for(i = CON_LINES_COUNT - 1; i >= 0; --i)
1793         if((CON_LINES(i).mask & mask_must) == mask_must)
1794         if((CON_LINES(i).mask & mask_mustnot) == 0)
1795         {
1796                 int h = Con_LineHeight(i);
1797
1798                 // line is the last visible line?
1799                 *last = i;
1800                 if(lines_seen + h > con_backscroll && lines_seen <= con_backscroll)
1801                 {
1802                         *limitlast = lines_seen + h - con_backscroll;
1803                         return;
1804                 }
1805
1806                 lines_seen += h;
1807         }
1808
1809         // if we get here, no line was on screen - scroll so that one line is
1810         // visible then.
1811         con_backscroll = lines_seen - 1;
1812         *limitlast = 1;
1813 }
1814
1815 /*
1816 ================
1817 Con_DrawConsole
1818
1819 Draws the console with the solid background
1820 The typing input line at the bottom should only be drawn if typing is allowed
1821 ================
1822 */
1823 void Con_DrawConsole (int lines)
1824 {
1825         float alpha, alpha0;
1826         double sx, sy;
1827         int mask_must = 0;
1828         int mask_mustnot = (developer.integer>0) ? 0 : CON_MASK_DEVELOPER;
1829         cachepic_t *conbackpic;
1830
1831         if (lines <= 0)
1832                 return;
1833
1834         if (con_backscroll < 0)
1835                 con_backscroll = 0;
1836
1837         con_vislines = lines;
1838
1839         r_draw2d_force = true;
1840
1841 // draw the background
1842         alpha0 = cls.signon == SIGNONS ? scr_conalpha.value : 1.0f; // always full alpha when not in game
1843         if((alpha = alpha0 * scr_conalphafactor.value) > 0)
1844         {
1845                 sx = scr_conscroll_x.value;
1846                 sy = scr_conscroll_y.value;
1847                 conbackpic = scr_conbrightness.value >= 0.01f ? Draw_CachePic_Flags("gfx/conback", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0) : NULL;
1848                 sx *= realtime; sy *= realtime;
1849                 sx -= floor(sx); sy -= floor(sy);
1850                 if (conbackpic && conbackpic->tex != r_texture_notexture)
1851                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1852                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1853                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1854                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1855                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1856                                         0);
1857                 else
1858                         DrawQ_Fill(0, lines - vid_conheight.integer, vid_conwidth.integer, vid_conheight.integer, 0.0f, 0.0f, 0.0f, alpha, 0);
1859         }
1860         if((alpha = alpha0 * scr_conalpha2factor.value) > 0)
1861         {
1862                 sx = scr_conscroll2_x.value;
1863                 sy = scr_conscroll2_y.value;
1864                 conbackpic = Draw_CachePic_Flags("gfx/conback2", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1865                 sx *= realtime; sy *= realtime;
1866                 sx -= floor(sx); sy -= floor(sy);
1867                 if(conbackpic && conbackpic->tex != r_texture_notexture)
1868                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1869                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1870                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1871                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1872                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1873                                         0);
1874         }
1875         if((alpha = alpha0 * scr_conalpha3factor.value) > 0)
1876         {
1877                 sx = scr_conscroll3_x.value;
1878                 sy = scr_conscroll3_y.value;
1879                 conbackpic = Draw_CachePic_Flags("gfx/conback3", (sx != 0 || sy != 0) ? CACHEPICFLAG_NOCLAMP : 0);
1880                 sx *= realtime; sy *= realtime;
1881                 sx -= floor(sx); sy -= floor(sy);
1882                 if(conbackpic && conbackpic->tex != r_texture_notexture)
1883                         DrawQ_SuperPic(0, lines - vid_conheight.integer, conbackpic, vid_conwidth.integer, vid_conheight.integer,
1884                                         0 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1885                                         1 + sx, 0 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1886                                         0 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1887                                         1 + sx, 1 + sy, scr_conbrightness.value, scr_conbrightness.value, scr_conbrightness.value, alpha,
1888                                         0);
1889         }
1890         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);
1891
1892 // draw the text
1893 #if 0
1894         {
1895                 int i;
1896                 int count = CON_LINES_COUNT;
1897                 float ymax = con_vislines - 2 * con_textsize.value;
1898                 float y = ymax + con_textsize.value * con_backscroll;
1899                 for (i = 0;i < count && y >= 0;i++)
1900                         y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y - con_textsize.value, CON_LINES_COUNT - 1 - i, 0, ymax) * con_textsize.value;
1901                 // fix any excessive scrollback for the next frame
1902                 if (i >= count && y >= 0)
1903                 {
1904                         con_backscroll -= (int)(y / con_textsize.value);
1905                         if (con_backscroll < 0)
1906                                 con_backscroll = 0;
1907                 }
1908         }
1909 #else
1910         if(CON_LINES_COUNT > 0)
1911         {
1912                 int i, last, limitlast;
1913                 float y;
1914                 float ymax = con_vislines - 2 * con_textsize.value;
1915                 Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1916                 //Con_LastVisibleLine(mask_must, mask_mustnot, &last, &limitlast);
1917                 y = ymax - con_textsize.value;
1918
1919                 if(limitlast)
1920                         y += (CON_LINES(last).height - limitlast) * con_textsize.value;
1921                 i = last;
1922
1923                 for(;;)
1924                 {
1925                         y -= Con_DrawConsoleLine(mask_must, mask_mustnot, y, i, 0, ymax) * con_textsize.value;
1926                         if(i == 0)
1927                                 break; // top of console buffer
1928                         if(y < 0)
1929                                 break; // top of console window
1930                         limitlast = 0;
1931                         --i;
1932                 }
1933         }
1934 #endif
1935
1936 // draw the input prompt, user text, and cursor if desired
1937         Con_DrawInput ();
1938
1939         r_draw2d_force = false;
1940 }
1941
1942 /*
1943 GetMapList
1944
1945 Made by [515]
1946 Prints not only map filename, but also
1947 its format (q1/q2/q3/hl) and even its message
1948 */
1949 //[515]: here is an ugly hack.. two gotos... oh my... *but it works*
1950 //LordHavoc: rewrote bsp type detection, rewrote message extraction to do proper worldspawn parsing
1951 //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
1952 //LordHavoc: FIXME: man this GetMapList is STILL ugly code even after my cleanups...
1953 qboolean GetMapList (const char *s, char *completedname, int completednamebufferlength)
1954 {
1955         fssearch_t      *t;
1956         char            message[1024];
1957         int                     i, k, max, p, o, min;
1958         unsigned char *len;
1959         qfile_t         *f;
1960         unsigned char buf[1024];
1961
1962         dpsnprintf(message, sizeof(message), "maps/%s*.bsp", s);
1963         t = FS_Search(message, 1, true);
1964         if(!t)
1965                 return false;
1966         if (t->numfilenames > 1)
1967                 Con_Printf("^1 %i maps found :\n", t->numfilenames);
1968         len = (unsigned char *)Z_Malloc(t->numfilenames);
1969         min = 666;
1970         for(max=i=0;i<t->numfilenames;i++)
1971         {
1972                 k = (int)strlen(t->filenames[i]);
1973                 k -= 9;
1974                 if(max < k)
1975                         max = k;
1976                 else
1977                 if(min > k)
1978                         min = k;
1979                 len[i] = k;
1980         }
1981         o = (int)strlen(s);
1982         for(i=0;i<t->numfilenames;i++)
1983         {
1984                 int lumpofs = 0, lumplen = 0;
1985                 char *entities = NULL;
1986                 const char *data = NULL;
1987                 char keyname[64];
1988                 char entfilename[MAX_QPATH];
1989                 strlcpy(message, "^1**ERROR**^7", sizeof(message));
1990                 p = 0;
1991                 f = FS_OpenVirtualFile(t->filenames[i], true);
1992                 if(f)
1993                 {
1994                         memset(buf, 0, 1024);
1995                         FS_Read(f, buf, 1024);
1996                         if (!memcmp(buf, "IBSP", 4))
1997                         {
1998                                 p = LittleLong(((int *)buf)[1]);
1999                                 if (p == Q3BSPVERSION)
2000                                 {
2001                                         q3dheader_t *header = (q3dheader_t *)buf;
2002                                         lumpofs = LittleLong(header->lumps[Q3LUMP_ENTITIES].fileofs);
2003                                         lumplen = LittleLong(header->lumps[Q3LUMP_ENTITIES].filelen);
2004                                 }
2005                                 else if (p == Q2BSPVERSION)
2006                                 {
2007                                         q2dheader_t *header = (q2dheader_t *)buf;
2008                                         lumpofs = LittleLong(header->lumps[Q2LUMP_ENTITIES].fileofs);
2009                                         lumplen = LittleLong(header->lumps[Q2LUMP_ENTITIES].filelen);
2010                                 }
2011                         }
2012                         else if((p = BuffLittleLong(buf)) == BSPVERSION || p == 30)
2013                         {
2014                                 dheader_t *header = (dheader_t *)buf;
2015                                 lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
2016                                 lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
2017                         }
2018                         else
2019                                 p = 0;
2020                         strlcpy(entfilename, t->filenames[i], sizeof(entfilename));
2021                         memcpy(entfilename + strlen(entfilename) - 4, ".ent", 5);
2022                         entities = (char *)FS_LoadFile(entfilename, tempmempool, true, NULL);
2023                         if (!entities && lumplen >= 10)
2024                         {
2025                                 FS_Seek(f, lumpofs, SEEK_SET);
2026                                 entities = (char *)Z_Malloc(lumplen + 1);
2027                                 FS_Read(f, entities, lumplen);
2028                         }
2029                         if (entities)
2030                         {
2031                                 // if there are entities to parse, a missing message key just
2032                                 // means there is no title, so clear the message string now
2033                                 message[0] = 0;
2034                                 data = entities;
2035                                 for (;;)
2036                                 {
2037                                         int l;
2038                                         if (!COM_ParseToken_Simple(&data, false, false))
2039                                                 break;
2040                                         if (com_token[0] == '{')
2041                                                 continue;
2042                                         if (com_token[0] == '}')
2043                                                 break;
2044                                         // skip leading whitespace
2045                                         for (k = 0;com_token[k] && ISWHITESPACE(com_token[k]);k++);
2046                                         for (l = 0;l < (int)sizeof(keyname) - 1 && com_token[k+l] && !ISWHITESPACE(com_token[k+l]);l++)
2047                                                 keyname[l] = com_token[k+l];
2048                                         keyname[l] = 0;
2049                                         if (!COM_ParseToken_Simple(&data, false, false))
2050                                                 break;
2051                                         if (developer_extra.integer)
2052                                                 Con_DPrintf("key: %s %s\n", keyname, com_token);
2053                                         if (!strcmp(keyname, "message"))
2054                                         {
2055                                                 // get the message contents
2056                                                 strlcpy(message, com_token, sizeof(message));
2057                                                 break;
2058                                         }
2059                                 }
2060                         }
2061                 }
2062                 if (entities)
2063                         Z_Free(entities);
2064                 if(f)
2065                         FS_Close(f);
2066                 *(t->filenames[i]+len[i]+5) = 0;
2067                 switch(p)
2068                 {
2069                 case Q3BSPVERSION:      strlcpy((char *)buf, "Q3", sizeof(buf));break;
2070                 case Q2BSPVERSION:      strlcpy((char *)buf, "Q2", sizeof(buf));break;
2071                 case BSPVERSION:        strlcpy((char *)buf, "Q1", sizeof(buf));break;
2072                 case 30:                        strlcpy((char *)buf, "HL", sizeof(buf));break;
2073                 default:                        strlcpy((char *)buf, "??", sizeof(buf));break;
2074                 }
2075                 Con_Printf("%16s (%s) %s\n", t->filenames[i]+5, buf, message);
2076         }
2077         Con_Print("\n");
2078         for(p=o;p<min;p++)
2079         {
2080                 k = *(t->filenames[0]+5+p);
2081                 if(k == 0)
2082                         goto endcomplete;
2083                 for(i=1;i<t->numfilenames;i++)
2084                         if(*(t->filenames[i]+5+p) != k)
2085                                 goto endcomplete;
2086         }
2087 endcomplete:
2088         if(p > o && completedname && completednamebufferlength > 0)
2089         {
2090                 memset(completedname, 0, completednamebufferlength);
2091                 memcpy(completedname, (t->filenames[0]+5), min(p, completednamebufferlength - 1));
2092         }
2093         Z_Free(len);
2094         FS_FreeSearch(t);
2095         return p > o;
2096 }
2097
2098 /*
2099         Con_DisplayList
2100
2101         New function for tab-completion system
2102         Added by EvilTypeGuy
2103         MEGA Thanks to Taniwha
2104
2105 */
2106 void Con_DisplayList(const char **list)
2107 {
2108         int i = 0, pos = 0, len = 0, maxlen = 0, width = (con_linewidth - 4);
2109         const char **walk = list;
2110
2111         while (*walk) {
2112                 len = (int)strlen(*walk);
2113                 if (len > maxlen)
2114                         maxlen = len;
2115                 walk++;
2116         }
2117         maxlen += 1;
2118
2119         while (*list) {
2120                 len = (int)strlen(*list);
2121                 if (pos + maxlen >= width) {
2122                         Con_Print("\n");
2123                         pos = 0;
2124                 }
2125
2126                 Con_Print(*list);
2127                 for (i = 0; i < (maxlen - len); i++)
2128                         Con_Print(" ");
2129
2130                 pos += maxlen;
2131                 list++;
2132         }
2133
2134         if (pos)
2135                 Con_Print("\n\n");
2136 }
2137
2138 /*
2139         SanitizeString strips color tags from the string in
2140         and writes the result on string out
2141 */
2142 void SanitizeString(char *in, char *out)
2143 {
2144         while(*in)
2145         {
2146                 if(*in == STRING_COLOR_TAG)
2147                 {
2148                         ++in;
2149                         if(!*in)
2150                         {
2151                                 out[0] = STRING_COLOR_TAG;
2152                                 out[1] = 0;
2153                                 return;
2154                         }
2155                         else if (*in >= '0' && *in <= '9') // ^[0-9] found
2156                         {
2157                                 ++in;
2158                                 if(!*in)
2159                                 {
2160                                         *out = 0;
2161                                         return;
2162                                 } else if (*in == STRING_COLOR_TAG) // ^[0-9]^ found, don't print ^[0-9]
2163                                         continue;
2164                         }
2165                         else if (*in == STRING_COLOR_RGB_TAG_CHAR) // ^x found
2166                         {
2167                                 if ( isxdigit(in[1]) && isxdigit(in[2]) && isxdigit(in[3]) )
2168                                 {
2169                                         in+=4;
2170                                         if (!*in)
2171                                         {
2172                                                 *out = 0;
2173                                                 return;
2174                                         } else if (*in == STRING_COLOR_TAG) // ^xrgb^ found, don't print ^xrgb
2175                                                 continue;
2176                                 }
2177                                 else in--;
2178                         }
2179                         else if (*in != STRING_COLOR_TAG)
2180                                 --in;
2181                 }
2182                 *out = qfont_table[*(unsigned char*)in];
2183                 ++in;
2184                 ++out;
2185         }
2186         *out = 0;
2187 }
2188
2189 // Now it becomes TRICKY :D --blub
2190 static char Nicks_list[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];     // contains the nicks with colors and all that
2191 static char Nicks_sanlist[MAX_SCOREBOARD][MAX_SCOREBOARDNAME];  // sanitized list for completion when there are other possible matches.
2192 // means: when somebody uses a cvar's name as his name, we won't ever get his colors in there...
2193 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
2194 static int Nicks_matchpos;
2195
2196 // co against <<:BLASTER:>> is true!?
2197 int Nicks_strncasecmp_nospaces(char *a, char *b, unsigned int a_len)
2198 {
2199         while(a_len)
2200         {
2201                 if(tolower(*a) == tolower(*b))
2202                 {
2203                         if(*a == 0)
2204                                 return 0;
2205                         --a_len;
2206                         ++a;
2207                         ++b;
2208                         continue;
2209                 }
2210                 if(!*a)
2211                         return -1;
2212                 if(!*b)
2213                         return 1;
2214                 if(*a == ' ')
2215                         return (*a < *b) ? -1 : 1;
2216                 if(*b == ' ')
2217                         ++b;
2218                 else
2219                         return (*a < *b) ? -1 : 1;
2220         }
2221         return 0;
2222 }
2223 int Nicks_strncasecmp(char *a, char *b, unsigned int a_len)
2224 {
2225         char space_char;
2226         if(!(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY))
2227         {
2228                 if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2229                         return Nicks_strncasecmp_nospaces(a, b, a_len);
2230                 return strncasecmp(a, b, a_len);
2231         }
2232
2233         space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' ';
2234
2235         // ignore non alphanumerics of B
2236         // if A contains a non-alphanumeric, B must contain it as well though!
2237         while(a_len)
2238         {
2239                 qboolean alnum_a, alnum_b;
2240
2241                 if(tolower(*a) == tolower(*b))
2242                 {
2243                         if(*a == 0) // end of both strings, they're equal
2244                                 return 0;
2245                         --a_len;
2246                         ++a;
2247                         ++b;
2248                         continue;
2249                 }
2250                 // not equal, end of one string?
2251                 if(!*a)
2252                         return -1;
2253                 if(!*b)
2254                         return 1;
2255                 // ignore non alphanumerics
2256                 alnum_a = ( (*a >= 'a' && *a <= 'z') || (*a >= 'A' && *a <= 'Z') || (*a >= '0' && *a <= '9') || *a == space_char);
2257                 alnum_b = ( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char);
2258                 if(!alnum_a) // b must contain this
2259                         return (*a < *b) ? -1 : 1;
2260                 if(!alnum_b)
2261                         ++b;
2262                 // otherwise, both are alnum, they're just not equal, return the appropriate number
2263                 else
2264                         return (*a < *b) ? -1 : 1;
2265         }
2266         return 0;
2267 }
2268
2269
2270 /* Nicks_CompleteCountPossible
2271
2272    Count the number of possible nicks to complete
2273  */
2274 int Nicks_CompleteCountPossible(char *line, int pos, char *s, qboolean isCon)
2275 {
2276         char name[128];
2277         int i, p;
2278         int match;
2279         int spos;
2280         int count = 0;
2281
2282         if(!con_nickcompletion.integer)
2283                 return 0;
2284
2285         // changed that to 1
2286         if(!line[0])// || !line[1]) // we want at least... 2 written characters
2287                 return 0;
2288
2289         for(i = 0; i < cl.maxclients; ++i)
2290         {
2291                 p = i;
2292                 if(!cl.scores[p].name[0])
2293                         continue;
2294
2295                 SanitizeString(cl.scores[p].name, name);
2296                 //Con_Printf(" ^2Sanitized: ^7%s -> %s", cl.scores[p].name, name);
2297
2298                 if(!name[0])
2299                         continue;
2300
2301                 match = -1;
2302                 spos = pos - 1; // no need for a minimum of characters :)
2303
2304                 while(spos >= 0)
2305                 {
2306                         if(spos > 0 && line[spos-1] != ' ' && line[spos-1] != ';' && line[spos-1] != '\"' && line[spos-1] != '\'')
2307                         {
2308                                 if(!(isCon && line[spos-1] == ']' && spos == 1) && // console start
2309                                    !(spos > 1 && line[spos-1] >= '0' && line[spos-1] <= '9' && line[spos-2] == STRING_COLOR_TAG)) // color start
2310                                 {
2311                                         --spos;
2312                                         continue;
2313                                 }
2314                         }
2315                         if(isCon && spos == 0)
2316                                 break;
2317                         if(Nicks_strncasecmp(line+spos, name, pos-spos) == 0)
2318                                 match = spos;
2319                         --spos;
2320                 }
2321                 if(match < 0)
2322                         continue;
2323                 //Con_Printf("Possible match: %s|%s\n", cl.scores[p].name, name);
2324                 strlcpy(Nicks_list[count], cl.scores[p].name, sizeof(Nicks_list[count]));
2325
2326                 // the sanitized list
2327                 strlcpy(Nicks_sanlist[count], name, sizeof(Nicks_sanlist[count]));
2328                 if(!count)
2329                 {
2330                         Nicks_matchpos = match;
2331                 }
2332
2333                 Nicks_offset[count] = s - (&line[match]);
2334                 //Con_Printf("offset for %s: %i\n", name, Nicks_offset[count]);
2335
2336                 ++count;
2337         }
2338         return count;
2339 }
2340
2341 void Cmd_CompleteNicksPrint(int count)
2342 {
2343         int i;
2344         for(i = 0; i < count; ++i)
2345                 Con_Printf("%s\n", Nicks_list[i]);
2346 }
2347
2348 void Nicks_CutMatchesNormal(int count)
2349 {
2350         // cut match 0 down to the longest possible completion
2351         int i;
2352         unsigned int c, l;
2353         c = strlen(Nicks_sanlist[0]) - 1;
2354         for(i = 1; i < count; ++i)
2355         {
2356                 l = strlen(Nicks_sanlist[i]) - 1;
2357                 if(l < c)
2358                         c = l;
2359
2360                 for(l = 0; l <= c; ++l)
2361                         if(tolower(Nicks_sanlist[0][l]) != tolower(Nicks_sanlist[i][l]))
2362                         {
2363                                 c = l-1;
2364                                 break;
2365                         }
2366         }
2367         Nicks_sanlist[0][c+1] = 0;
2368         //Con_Printf("List0: %s\n", Nicks_sanlist[0]);
2369 }
2370
2371 unsigned int Nicks_strcleanlen(const char *s)
2372 {
2373         unsigned int l = 0;
2374         while(*s)
2375         {
2376                 if( (*s >= 'a' && *s <= 'z') ||
2377                     (*s >= 'A' && *s <= 'Z') ||
2378                     (*s >= '0' && *s <= '9') ||
2379                     *s == ' ')
2380                         ++l;
2381                 ++s;
2382         }
2383         return l;
2384 }
2385
2386 void Nicks_CutMatchesAlphaNumeric(int count)
2387 {
2388         // cut match 0 down to the longest possible completion
2389         int i;
2390         unsigned int c, l;
2391         char tempstr[sizeof(Nicks_sanlist[0])];
2392         char *a, *b;
2393         char space_char = (con_nickcompletion_flags.integer & NICKS_NO_SPACES) ? 'a' : ' '; // yes this is correct, we want NO spaces when no spaces
2394
2395         c = strlen(Nicks_sanlist[0]);
2396         for(i = 0, l = 0; i < (int)c; ++i)
2397         {
2398                 if( (Nicks_sanlist[0][i] >= 'a' && Nicks_sanlist[0][i] <= 'z') ||
2399                     (Nicks_sanlist[0][i] >= 'A' && Nicks_sanlist[0][i] <= 'Z') ||
2400                     (Nicks_sanlist[0][i] >= '0' && Nicks_sanlist[0][i] <= '9') || Nicks_sanlist[0][i] == space_char) // this is what's COPIED
2401                 {
2402                         tempstr[l++] = Nicks_sanlist[0][i];
2403                 }
2404         }
2405         tempstr[l] = 0;
2406
2407         for(i = 1; i < count; ++i)
2408         {
2409                 a = tempstr;
2410                 b = Nicks_sanlist[i];
2411                 while(1)
2412                 {
2413                         if(!*a)
2414                                 break;
2415                         if(!*b)
2416                         {
2417                                 *a = 0;
2418                                 break;
2419                         }
2420                         if(tolower(*a) == tolower(*b))
2421                         {
2422                                 ++a;
2423                                 ++b;
2424                                 continue;
2425                         }
2426                         if( (*b >= 'a' && *b <= 'z') || (*b >= 'A' && *b <= 'Z') || (*b >= '0' && *b <= '9') || *b == space_char)
2427                         {
2428                                 // b is alnum, so cut
2429                                 *a = 0;
2430                                 break;
2431                         }
2432                         ++b;
2433                 }
2434         }
2435         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2436         Nicks_CutMatchesNormal(count);
2437         //if(!Nicks_sanlist[0][0])
2438         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2439         {
2440                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2441                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2442         }
2443 }
2444
2445 void Nicks_CutMatchesNoSpaces(int count)
2446 {
2447         // cut match 0 down to the longest possible completion
2448         int i;
2449         unsigned int c, l;
2450         char tempstr[sizeof(Nicks_sanlist[0])];
2451         char *a, *b;
2452
2453         c = strlen(Nicks_sanlist[0]);
2454         for(i = 0, l = 0; i < (int)c; ++i)
2455         {
2456                 if(Nicks_sanlist[0][i] != ' ') // here it's what's NOT copied
2457                 {
2458                         tempstr[l++] = Nicks_sanlist[0][i];
2459                 }
2460         }
2461         tempstr[l] = 0;
2462
2463         for(i = 1; i < count; ++i)
2464         {
2465                 a = tempstr;
2466                 b = Nicks_sanlist[i];
2467                 while(1)
2468                 {
2469                         if(!*a)
2470                                 break;
2471                         if(!*b)
2472                         {
2473                                 *a = 0;
2474                                 break;
2475                         }
2476                         if(tolower(*a) == tolower(*b))
2477                         {
2478                                 ++a;
2479                                 ++b;
2480                                 continue;
2481                         }
2482                         if(*b != ' ')
2483                         {
2484                                 *a = 0;
2485                                 break;
2486                         }
2487                         ++b;
2488                 }
2489         }
2490         // Just so you know, if cutmatchesnormal doesn't kill the first entry, then even the non-alnums fit
2491         Nicks_CutMatchesNormal(count);
2492         //if(!Nicks_sanlist[0][0])
2493         //Con_Printf("TS: %s\n", tempstr);
2494         if(Nicks_strcleanlen(Nicks_sanlist[0]) < strlen(tempstr))
2495         {
2496                 // if the clean sanitized one is longer than the current one, use it, it has crap chars which definitely are in there
2497                 strlcpy(Nicks_sanlist[0], tempstr, sizeof(tempstr));
2498         }
2499 }
2500
2501 void Nicks_CutMatches(int count)
2502 {
2503         if(con_nickcompletion_flags.integer & NICKS_ALPHANUMERICS_ONLY)
2504                 Nicks_CutMatchesAlphaNumeric(count);
2505         else if(con_nickcompletion_flags.integer & NICKS_NO_SPACES)
2506                 Nicks_CutMatchesNoSpaces(count);
2507         else
2508                 Nicks_CutMatchesNormal(count);
2509 }
2510
2511 const char **Nicks_CompleteBuildList(int count)
2512 {
2513         const char **buf;
2514         int bpos = 0;
2515         // the list is freed by Con_CompleteCommandLine, so create a char**
2516         buf = (const char **)Mem_Alloc(tempmempool, count * sizeof(const char *) + sizeof (const char *));
2517
2518         for(; bpos < count; ++bpos)
2519                 buf[bpos] = Nicks_sanlist[bpos] + Nicks_offset[bpos];
2520
2521         Nicks_CutMatches(count);
2522
2523         buf[bpos] = NULL;
2524         return buf;
2525 }
2526
2527 /*
2528         Nicks_AddLastColor
2529         Restores the previous used color, after the autocompleted name.
2530 */
2531 int Nicks_AddLastColor(char *buffer, int pos)
2532 {
2533         qboolean quote_added = false;
2534         int match;
2535         int color = STRING_COLOR_DEFAULT + '0';
2536         char r = 0, g = 0, b = 0;
2537
2538         if(con_nickcompletion_flags.integer & NICKS_ADD_QUOTE && buffer[Nicks_matchpos-1] == '\"')
2539         {
2540                 // we'll have to add a quote :)
2541                 buffer[pos++] = '\"';
2542                 quote_added = true;
2543         }
2544
2545         if((!quote_added && con_nickcompletion_flags.integer & NICKS_ADD_COLOR) || con_nickcompletion_flags.integer & NICKS_FORCE_COLOR)
2546         {
2547                 // add color when no quote was added, or when flags &4?
2548                 // find last color
2549                 for(match = Nicks_matchpos-1; match >= 0; --match)
2550                 {
2551                         if(buffer[match] == STRING_COLOR_TAG)
2552                         {
2553                                 if( isdigit(buffer[match+1]) )
2554                                 {
2555                                         color = buffer[match+1];
2556                                         break;
2557                                 }
2558                                 else if(buffer[match+1] == STRING_COLOR_RGB_TAG_CHAR)
2559                                 {
2560                                         if ( isxdigit(buffer[match+2]) && isxdigit(buffer[match+3]) && isxdigit(buffer[match+4]) )
2561                                         {
2562                                                 r = buffer[match+2];
2563                                                 g = buffer[match+3];
2564                                                 b = buffer[match+4];
2565                                                 color = -1;
2566                                                 break;
2567                                         }
2568                                 }
2569                         }
2570                 }
2571                 if(!quote_added)
2572                 {
2573                         if( pos >= 2 && buffer[pos-2] == STRING_COLOR_TAG && isdigit(buffer[pos-1]) ) // when thes use &4
2574                                 pos -= 2;
2575                         else if( pos >= 5 && buffer[pos-5] == STRING_COLOR_TAG && buffer[pos-4] == STRING_COLOR_RGB_TAG_CHAR
2576                                          && isxdigit(buffer[pos-3]) && isxdigit(buffer[pos-2]) && isxdigit(buffer[pos-1]) )
2577                                 pos -= 5;
2578                 }
2579                 buffer[pos++] = STRING_COLOR_TAG;
2580                 if (color == -1)
2581                 {
2582                         buffer[pos++] = STRING_COLOR_RGB_TAG_CHAR;
2583                         buffer[pos++] = r;
2584                         buffer[pos++] = g;
2585                         buffer[pos++] = b;
2586                 }
2587                 else
2588                         buffer[pos++] = color;
2589         }
2590         return pos;
2591 }
2592
2593 int Nicks_CompleteChatLine(char *buffer, size_t size, unsigned int pos)
2594 {
2595         int n;
2596         /*if(!con_nickcompletion.integer)
2597           return; is tested in Nicks_CompletionCountPossible */
2598         n = Nicks_CompleteCountPossible(buffer, pos, &buffer[pos], false);
2599         if(n == 1)
2600         {
2601                 size_t len;
2602                 char *msg;
2603
2604                 msg = Nicks_list[0];
2605                 len = min(size - Nicks_matchpos - 3, strlen(msg));
2606                 memcpy(&buffer[Nicks_matchpos], msg, len);
2607                 if( len < (size - 7) ) // space for color (^[0-9] or ^xrgb) and space and \0
2608                         len = Nicks_AddLastColor(buffer, Nicks_matchpos+len);
2609                 buffer[len++] = ' ';
2610                 buffer[len] = 0;
2611                 return len;
2612         } else if(n > 1)
2613         {
2614                 int len;
2615                 char *msg;
2616                 Con_Printf("\n%i possible nicks:\n", n);
2617                 Cmd_CompleteNicksPrint(n);
2618
2619                 Nicks_CutMatches(n);
2620
2621                 msg = Nicks_sanlist[0];
2622                 len = min(size - Nicks_matchpos, strlen(msg));
2623                 memcpy(&buffer[Nicks_matchpos], msg, len);
2624                 buffer[Nicks_matchpos + len] = 0;
2625                 //pos += len;
2626                 return Nicks_matchpos + len;
2627         }
2628         return pos;
2629 }
2630
2631
2632 /*
2633         Con_CompleteCommandLine
2634
2635         New function for tab-completion system
2636         Added by EvilTypeGuy
2637         Thanks to Fett erich@heintz.com
2638         Thanks to taniwha
2639         Enhanced to tab-complete map names by [515]
2640
2641 */
2642 void Con_CompleteCommandLine (void)
2643 {
2644         const char *cmd = "";
2645         char *s;
2646         const char **list[4] = {0, 0, 0, 0};
2647         char s2[512];
2648         char command[512];
2649         int c, v, a, i, cmd_len, pos, k;
2650         int n; // nicks --blub
2651         const char *space, *patterns;
2652
2653         //find what we want to complete
2654         pos = key_linepos;
2655         while(--pos)
2656         {
2657                 k = key_line[pos];
2658                 if(k == '\"' || k == ';' || k == ' ' || k == '\'')
2659                         break;
2660         }
2661         pos++;
2662
2663         s = key_line + pos;
2664         strlcpy(s2, key_line + key_linepos, sizeof(s2));        //save chars after cursor
2665         key_line[key_linepos] = 0;                                      //hide them
2666
2667         space = strchr(key_line + 1, ' ');
2668         if(space && pos == (space - key_line) + 1)
2669         {
2670                 strlcpy(command, key_line + 1, min(sizeof(command), (unsigned int)(space - key_line)));
2671
2672                 patterns = Cvar_VariableString(va("con_completion_%s", command)); // TODO maybe use a better place for this?
2673                 if(patterns && !*patterns)
2674                         patterns = NULL; // get rid of the empty string
2675
2676                 if(!strcmp(command, "map") || !strcmp(command, "changelevel") || (patterns && !strcmp(patterns, "map")))
2677                 {
2678                         //maps search
2679                         char t[MAX_QPATH];
2680                         if (GetMapList(s, t, sizeof(t)))
2681                         {
2682                                 // first move the cursor
2683                                 key_linepos += (int)strlen(t) - (int)strlen(s);
2684
2685                                 // and now do the actual work
2686                                 *s = 0;
2687                                 strlcat(key_line, t, MAX_INPUTLINE);
2688                                 strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2689
2690                                 // and fix the cursor
2691                                 if(key_linepos > (int) strlen(key_line))
2692                                         key_linepos = (int) strlen(key_line);
2693                         }
2694                         return;
2695                 }
2696                 else
2697                 {
2698                         if(patterns)
2699                         {
2700                                 char t[MAX_QPATH];
2701                                 stringlist_t resultbuf, dirbuf;
2702
2703                                 // Usage:
2704                                 //   // store completion patterns (space separated) for command foo in con_completion_foo
2705                                 //   set con_completion_foo "foodata/*.foodefault *.foo"
2706                                 //   foo <TAB>
2707                                 //
2708                                 // Note: patterns with slash are always treated as absolute
2709                                 // patterns; patterns without slash search in the innermost
2710                                 // directory the user specified. There is no way to "complete into"
2711                                 // a directory as of now, as directories seem to be unknown to the
2712                                 // FS subsystem.
2713                                 //
2714                                 // Examples:
2715                                 //   set con_completion_playermodel "models/player/*.zym models/player/*.md3 models/player/*.psk models/player/*.dpm"
2716                                 //   set con_completion_playdemo "*.dem"
2717                                 //   set con_completion_play "*.wav *.ogg"
2718                                 //
2719                                 // TODO somehow add support for directories; these shall complete
2720                                 // to their name + an appended slash.
2721
2722                                 stringlistinit(&resultbuf);
2723                                 stringlistinit(&dirbuf);
2724                                 while(COM_ParseToken_Simple(&patterns, false, false))
2725                                 {
2726                                         fssearch_t *search;
2727                                         if(strchr(com_token, '/'))
2728                                         {
2729                                                 search = FS_Search(com_token, true, true);
2730                                         }
2731                                         else
2732                                         {
2733                                                 const char *slash = strrchr(s, '/');
2734                                                 if(slash)
2735                                                 {
2736                                                         strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2737                                                         strlcat(t, com_token, sizeof(t));
2738                                                         search = FS_Search(t, true, true);
2739                                                 }
2740                                                 else
2741                                                         search = FS_Search(com_token, true, true);
2742                                         }
2743                                         if(search)
2744                                         {
2745                                                 for(i = 0; i < search->numfilenames; ++i)
2746                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2747                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_FILE)
2748                                                                         stringlistappend(&resultbuf, search->filenames[i]);
2749                                                 FS_FreeSearch(search);
2750                                         }
2751                                 }
2752
2753                                 // In any case, add directory names
2754                                 {
2755                                         fssearch_t *search;
2756                                         const char *slash = strrchr(s, '/');
2757                                         if(slash)
2758                                         {
2759                                                 strlcpy(t, s, min(sizeof(t), (unsigned int)(slash - s + 2))); // + 2, because I want to include the slash
2760                                                 strlcat(t, "*", sizeof(t));
2761                                                 search = FS_Search(t, true, true);
2762                                         }
2763                                         else
2764                                                 search = FS_Search("*", true, true);
2765                                         if(search)
2766                                         {
2767                                                 for(i = 0; i < search->numfilenames; ++i)
2768                                                         if(!strncmp(search->filenames[i], s, strlen(s)))
2769                                                                 if(FS_FileType(search->filenames[i]) == FS_FILETYPE_DIRECTORY)
2770                                                                         stringlistappend(&dirbuf, search->filenames[i]);
2771                                                 FS_FreeSearch(search);
2772                                         }
2773                                 }
2774
2775                                 if(resultbuf.numstrings > 0 || dirbuf.numstrings > 0)
2776                                 {
2777                                         const char *p, *q;
2778                                         unsigned int matchchars;
2779                                         if(resultbuf.numstrings == 0 && dirbuf.numstrings == 1)
2780                                         {
2781                                                 dpsnprintf(t, sizeof(t), "%s/", dirbuf.strings[0]);
2782                                         }
2783                                         else
2784                                         if(resultbuf.numstrings == 1 && dirbuf.numstrings == 0)
2785                                         {
2786                                                 dpsnprintf(t, sizeof(t), "%s ", resultbuf.strings[0]);
2787                                         }
2788                                         else
2789                                         {
2790                                                 stringlistsort(&resultbuf); // dirbuf is already sorted
2791                                                 Con_Printf("\n%i possible filenames\n", resultbuf.numstrings + dirbuf.numstrings);
2792                                                 for(i = 0; i < dirbuf.numstrings; ++i)
2793                                                 {
2794                                                         Con_Printf("%s/\n", dirbuf.strings[i]);
2795                                                 }
2796                                                 for(i = 0; i < resultbuf.numstrings; ++i)
2797                                                 {
2798                                                         Con_Printf("%s\n", resultbuf.strings[i]);
2799                                                 }
2800                                                 matchchars = sizeof(t) - 1;
2801                                                 if(resultbuf.numstrings > 0)
2802                                                 {
2803                                                         p = resultbuf.strings[0];
2804                                                         q = resultbuf.strings[resultbuf.numstrings - 1];
2805                                                         for(; *p && *p == *q; ++p, ++q);
2806                                                         matchchars = (unsigned int)(p - resultbuf.strings[0]);
2807                                                 }
2808                                                 if(dirbuf.numstrings > 0)
2809                                                 {
2810                                                         p = dirbuf.strings[0];
2811                                                         q = dirbuf.strings[dirbuf.numstrings - 1];
2812                                                         for(; *p && *p == *q; ++p, ++q);
2813                                                         matchchars = min(matchchars, (unsigned int)(p - dirbuf.strings[0]));
2814                                                 }
2815                                                 // now p points to the first non-equal character, or to the end
2816                                                 // of resultbuf.strings[0]. We want to append the characters
2817                                                 // from resultbuf.strings[0] to (not including) p as these are
2818                                                 // the unique prefix
2819                                                 strlcpy(t, (resultbuf.numstrings > 0 ? resultbuf : dirbuf).strings[0], min(matchchars + 1, sizeof(t)));
2820                                         }
2821
2822                                         // first move the cursor
2823                                         key_linepos += (int)strlen(t) - (int)strlen(s);
2824
2825                                         // and now do the actual work
2826                                         *s = 0;
2827                                         strlcat(key_line, t, MAX_INPUTLINE);
2828                                         strlcat(key_line, s2, MAX_INPUTLINE); //add back chars after cursor
2829
2830                                         // and fix the cursor
2831                                         if(key_linepos > (int) strlen(key_line))
2832                                                 key_linepos = (int) strlen(key_line);
2833                                 }
2834                                 stringlistfreecontents(&resultbuf);
2835                                 stringlistfreecontents(&dirbuf);
2836
2837                                 return; // bail out, when we complete for a command that wants a file name
2838                         }
2839                 }
2840         }
2841
2842         // Count number of possible matches and print them
2843         c = Cmd_CompleteCountPossible(s);
2844         if (c)
2845         {
2846                 Con_Printf("\n%i possible command%s\n", c, (c > 1) ? "s: " : ":");
2847                 Cmd_CompleteCommandPrint(s);
2848         }
2849         v = Cvar_CompleteCountPossible(s);
2850         if (v)
2851         {
2852                 Con_Printf("\n%i possible variable%s\n", v, (v > 1) ? "s: " : ":");
2853                 Cvar_CompleteCvarPrint(s);
2854         }
2855         a = Cmd_CompleteAliasCountPossible(s);
2856         if (a)
2857         {
2858                 Con_Printf("\n%i possible alias%s\n", a, (a > 1) ? "es: " : ":");
2859                 Cmd_CompleteAliasPrint(s);
2860         }
2861         n = Nicks_CompleteCountPossible(key_line, key_linepos, s, true);
2862         if (n)
2863         {
2864                 Con_Printf("\n%i possible nick%s\n", n, (n > 1) ? "s: " : ":");
2865                 Cmd_CompleteNicksPrint(n);
2866         }
2867
2868         if (!(c + v + a + n))   // No possible matches
2869         {
2870                 if(s2[0])
2871                         strlcpy(&key_line[key_linepos], s2, sizeof(key_line) - key_linepos);
2872                 return;
2873         }
2874
2875         if (c)
2876                 cmd = *(list[0] = Cmd_CompleteBuildList(s));
2877         if (v)
2878                 cmd = *(list[1] = Cvar_CompleteBuildList(s));
2879         if (a)
2880                 cmd = *(list[2] = Cmd_CompleteAliasBuildList(s));
2881         if (n)
2882                 cmd = *(list[3] = Nicks_CompleteBuildList(n));
2883
2884         for (cmd_len = (int)strlen(s);;cmd_len++)
2885         {
2886                 const char **l;
2887                 for (i = 0; i < 3; i++)
2888                         if (list[i])
2889                                 for (l = list[i];*l;l++)
2890                                         if ((*l)[cmd_len] != cmd[cmd_len])
2891                                                 goto done;
2892                 // all possible matches share this character, so we continue...
2893                 if (!cmd[cmd_len])
2894                 {
2895                         // if all matches ended at the same position, stop
2896                         // (this means there is only one match)
2897                         break;
2898                 }
2899         }
2900 done:
2901
2902         // prevent a buffer overrun by limiting cmd_len according to remaining space
2903         cmd_len = min(cmd_len, (int)sizeof(key_line) - 1 - pos);
2904         if (cmd)
2905         {
2906                 key_linepos = pos;
2907                 memcpy(&key_line[key_linepos], cmd, cmd_len);
2908                 key_linepos += cmd_len;
2909                 // if there is only one match, add a space after it
2910                 if (c + v + a + n == 1 && key_linepos < (int)sizeof(key_line) - 1)
2911                 {
2912                         if(n)
2913                         { // was a nick, might have an offset, and needs colors ;) --blub
2914                                 key_linepos = pos - Nicks_offset[0];
2915                                 cmd_len = strlen(Nicks_list[0]);
2916                                 cmd_len = min(cmd_len, (int)sizeof(key_line) - 3 - pos);
2917
2918                                 memcpy(&key_line[key_linepos] , Nicks_list[0], cmd_len);
2919                                 key_linepos += cmd_len;
2920                                 if(key_linepos < (int)(sizeof(key_line)-4)) // space for ^, X and space and \0
2921                                         key_linepos = Nicks_AddLastColor(key_line, key_linepos);
2922                         }
2923                         key_line[key_linepos++] = ' ';
2924                 }
2925         }
2926
2927         // use strlcat to avoid a buffer overrun
2928         key_line[key_linepos] = 0;
2929         strlcat(key_line, s2, sizeof(key_line));
2930
2931         // free the command, cvar, and alias lists
2932         for (i = 0; i < 4; i++)
2933                 if (list[i])
2934                         Mem_Free((void *)list[i]);
2935 }
2936