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