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 #include <string.h>
23
24 #include "client.h"
25 #include "cmd.h"
26 #include "console.h"
27 #include "draw.h"
28 #include "keys.h"
29 #include "quakedef.h"
30 #include "screen.h"
31 #include "sys.h"
32 #include "zone.h"
33
34 #ifdef NQ_HACK
35 #include "host.h"
36 #include "sound.h"
37 #endif
38
39 #define CON_TEXTSIZE 16384
40 #define NUM_CON_TIMES 4
41
42 console_t *con; // point to current console
43 static console_t con_main;
44
45 int con_ormask = 0;
46 qboolean con_forcedup;
47 int con_totallines; // total lines in console scrollback
48 int con_notifylines; // scan lines to clear for notify lines
49
50 static int con_linewidth; // characters across screen
51 static int con_vislines;
52
53 int
Con_GetWidth(void)54 Con_GetWidth(void)
55 {
56 return con_linewidth;
57 }
58
59 static float con_cursorspeed = 4;
60 static cvar_t con_notifytime = { "con_notifytime", "3" }; //seconds
61
62 static float con_times[NUM_CON_TIMES]; // realtime time the line was generated
63 // for transparent notify lines
64
65 static qboolean debuglog;
66
67 qboolean con_initialized;
68
69 /*
70 ====================
71 Con_ToggleConsole_f
72 ====================
73 */
74 void
Con_ToggleConsole_f(void)75 Con_ToggleConsole_f(void)
76 {
77 Key_ClearTyping();
78
79 if (key_dest == key_console) {
80 if (!con_forcedup) {
81 key_dest = key_game;
82 Key_ClearTyping();
83 }
84 } else
85 key_dest = key_console;
86
87 Con_ClearNotify();
88 }
89
90 /*
91 ================
92 Con_Clear_f
93 ================
94 */
95 void
Con_Clear_f(void)96 Con_Clear_f(void)
97 {
98 memset(con_main.text, ' ', CON_TEXTSIZE);
99 }
100
101
102 /*
103 ================
104 Con_ClearNotify
105 ================
106 */
107 void
Con_ClearNotify(void)108 Con_ClearNotify(void)
109 {
110 int i;
111
112 for (i = 0; i < NUM_CON_TIMES; i++)
113 con_times[i] = 0;
114 }
115
116
117 /*
118 ================
119 Con_MessageMode_f
120 ================
121 */
122 void
Con_MessageMode_f(void)123 Con_MessageMode_f(void)
124 {
125 key_dest = key_message;
126 chat_team = false;
127 }
128
129 /*
130 ================
131 Con_MessageMode2_f
132 ================
133 */
134 void
Con_MessageMode2_f(void)135 Con_MessageMode2_f(void)
136 {
137 key_dest = key_message;
138 chat_team = true;
139 }
140
141 /*
142 ================
143 Con_Resize
144
145 ================
146 */
Con_Resize(console_t * c)147 static void Con_Resize(console_t * c)
148 {
149 int width;
150 char tbuf[CON_TEXTSIZE];
151
152 width = (vid.width >> 3) - 2;
153
154 if (width == con_linewidth)
155 return;
156
157 if (width < 1) // video hasn't been initialized yet
158 {
159 width = 38;
160 con_linewidth = width;
161 con_totallines = CON_TEXTSIZE / con_linewidth;
162 memset(c->text, ' ', CON_TEXTSIZE);
163 }
164 else
165 {
166 int i, j, numlines, numchars;
167 int oldwidth = con_linewidth;
168 int oldtotallines = con_totallines;
169
170 con_linewidth = width;
171 con_totallines = CON_TEXTSIZE / con_linewidth;
172 numlines = oldtotallines;
173
174 if (con_totallines < numlines)
175 numlines = con_totallines;
176
177 numchars = oldwidth;
178
179 if (con_linewidth < numchars)
180 numchars = con_linewidth;
181
182 memcpy(tbuf, c->text, CON_TEXTSIZE);
183 memset(c->text, ' ', CON_TEXTSIZE);
184
185 for (i = 0; i < numlines; i++)
186 {
187 for (j = 0; j < numchars; j++)
188 {
189 c->text[(con_totallines - 1 - i) * con_linewidth + j] =
190 tbuf[((c->current - i + oldtotallines) %
191 oldtotallines) * oldwidth + j];
192 }
193 }
194 Con_ClearNotify();
195 }
196
197 c->current = con_totallines - 1;
198 c->display = c->current;
199 }
200
201 /*
202 ================
203 Con_CheckResize
204
205 If the line width has changed, reformat the buffer.
206 ================
207 */
208 void
Con_CheckResize(void)209 Con_CheckResize(void)
210 {
211 Con_Resize(&con_main);
212 }
213
214 /*
215 ===============
216 Con_Linefeed
217 ===============
218 */
219 void
Con_Linefeed(void)220 Con_Linefeed(void)
221 {
222 con->x = 0;
223 if (con->display == con->current)
224 con->display++;
225 con->current++;
226 memset(&con->text[(con->current % con_totallines) * con_linewidth]
227 , ' ', con_linewidth);
228 }
229
230 /*
231 ================
232 Con_Print
233
234 Handles cursor positioning, line wrapping, etc
235 All console printing must go through this in order to be logged to disk
236 If no console is visible, the notify window will pop up.
237 ================
238 */
239 void
Con_Print(const char * txt)240 Con_Print(const char *txt)
241 {
242 int y;
243 int c, l;
244 static int cr;
245 int mask;
246
247 if (txt[0] == 1 || txt[0] == 2) {
248 mask = 128; // go to colored text
249 txt++;
250 #ifdef NQ_HACK
251 if (txt[0] == 1)
252 S_LocalSound("misc/talk.wav"); // play talk wav
253 #endif
254 } else
255 mask = 0;
256
257 while ((c = *txt)) {
258 // count word length
259 for (l = 0; l < con_linewidth; l++)
260 if (txt[l] <= ' ')
261 break;
262
263 // word wrap
264 if (l != con_linewidth && (con->x + l > con_linewidth))
265 con->x = 0;
266
267 txt++;
268
269 if (cr) {
270 con->current--;
271 cr = false;
272 }
273
274
275 if (!con->x) {
276 Con_Linefeed();
277 // mark time for transparent overlay
278 if (con->current >= 0)
279 con_times[con->current % NUM_CON_TIMES] = realtime;
280 }
281
282 switch (c) {
283 case '\n':
284 con->x = 0;
285 break;
286
287 case '\r':
288 con->x = 0;
289 cr = 1;
290 break;
291
292 default: // display character and advance
293 y = con->current % con_totallines;
294 con->text[y * con_linewidth + con->x] = c | mask | con_ormask;
295 con->x++;
296 if (con->x >= con_linewidth)
297 con->x = 0;
298 break;
299 }
300 }
301 }
302
303
304 /*
305 ================
306 Con_Printf
307
308 Handles cursor positioning, line wrapping, etc
309 ================
310 */
Con_Printf(const char * fmt,...)311 void Con_Printf(const char *fmt, ...)
312 {
313 va_list argptr;
314 char msg[MAX_PRINTMSG];
315
316 va_start(argptr, fmt);
317 vsnprintf(msg, sizeof(msg), fmt, argptr);
318 va_end(argptr);
319
320 /* also echo to debugging console */
321 Sys_Printf("%s", msg); // also echo to debugging console
322
323 /* log all messages to file */
324 if (debuglog)
325 Sys_DebugLog(va("%s/qconsole.log", com_savedir), "%s", msg);
326
327 if (!con_initialized)
328 return;
329
330 #ifdef NQ_HACK
331 if (cls.state == ca_dedicated)
332 return; // no graphics mode
333 #endif
334
335 /* write it to the scrollable buffer */
336 Con_Print(msg);
337
338 /*
339 * FIXME - not sure if this is ok, need to rework the screen update
340 * criteria so it gets done once per frame unless explicitly flushed. For
341 * now, don't update until we see a newline char.
342 */
343 if (!strchr(msg, '\n'))
344 return;
345
346 // update the screen immediately if the console is displayed
347 #ifdef NQ_HACK
348 if (cls.state != ca_active && !scr_disabled_for_loading)
349 #else
350 if (con_forcedup)
351 #endif
352 {
353 static qboolean inupdate;
354 // protect against infinite loop if something in SCR_UpdateScreen calls
355 // Con_Printd
356 if (!inupdate)
357 {
358 inupdate = true;
359 SCR_UpdateScreen();
360 inupdate = false;
361 }
362 }
363 }
364
365 /*
366 ================
367 Con_DPrintf
368
369 A Con_Printf that only shows up if the "developer" cvar is set
370 ================
371 */
372 void
Con_DPrintf(const char * fmt,...)373 Con_DPrintf(const char *fmt, ...)
374 {
375 va_list argptr;
376 char msg[MAX_PRINTMSG];
377
378 if (!developer.value) {
379 if (debuglog) {
380 strcpy(msg, "DEBUG: ");
381 va_start(argptr, fmt);
382 vsnprintf(msg + 7, sizeof(msg) - 7, fmt, argptr);
383 va_end(argptr);
384 Sys_DebugLog(va("%s/qconsole.log", com_savedir), "%s", msg);
385 }
386 return;
387 }
388
389 va_start(argptr, fmt);
390 vsnprintf(msg, sizeof(msg), fmt, argptr);
391 va_end(argptr);
392
393 Con_Printf("%s", msg);
394 }
395
396
397 /*
398 ==============================================================================
399
400 DRAWING
401
402 ==============================================================================
403 */
404
405
406 /*
407 ================
408 Con_DrawInput
409
410 The input line scrolls horizontally if typing goes beyond the right edge
411 ================
412 */
413 void
Con_DrawInput(void)414 Con_DrawInput(void)
415 {
416 int y;
417 int i;
418 char *text;
419
420 if (key_dest != key_console && !con_forcedup)
421 return; // don't draw anything
422
423 text = key_lines[edit_line];
424
425 // add the cursor frame
426 text[key_linepos] = 10 + ((int)(realtime * con_cursorspeed) & 1);
427
428 // fill out remainder with spaces
429 for (i = key_linepos + 1; i < con_linewidth; i++)
430 text[i] = ' ';
431
432 // prestep if horizontally scrolling
433 if (key_linepos >= con_linewidth)
434 text += 1 + key_linepos - con_linewidth;
435
436 // draw it
437 y = con_vislines - 22;
438 for (i = 0; i < con_linewidth; i++)
439 Draw_Character((i + 1) << 3, y, text[i]);
440
441 // remove cursor
442 key_lines[edit_line][key_linepos] = 0;
443 }
444
445
446 /*
447 ================
448 Con_DrawNotify
449
450 Draws the last few lines of output transparently over the game top
451 ================
452 */
Con_DrawNotify(void)453 void Con_DrawNotify(void)
454 {
455 int i, x;
456 char *text;
457 float time;
458 char *s;
459 int v = 0;
460
461 for (i = con->current - NUM_CON_TIMES + 1; i <= con->current; i++)
462 {
463 if (i < 0)
464 continue;
465 time = con_times[i % NUM_CON_TIMES];
466 if (time == 0)
467 continue;
468 time = realtime - time;
469 if (time > con_notifytime.value)
470 continue;
471 text = con->text + (i % con_totallines) * con_linewidth;
472
473 clearnotify = 0;
474 scr_copytop = 1;
475
476 for (x = 0; x < con_linewidth; x++)
477 Draw_Character((x + 1) << 3, v, text[x]);
478
479 v += 8;
480 }
481
482
483 if (key_dest == key_message)
484 {
485 int skip;
486
487 clearnotify = 0;
488 scr_copytop = 1;
489
490 if (chat_team)
491 {
492 Draw_String(8, v, "say_team:");
493 skip = 11;
494 }
495 else
496 {
497 Draw_String(8, v, "say:");
498 skip = 6;
499 }
500
501 s = chat_buffer;
502 // FIXME = Truncating? should be while, not if?
503 if (chat_bufferlen > (vid.width >> 3) - (skip + 1))
504 s += chat_bufferlen - ((vid.width >> 3) - (skip + 1));
505
506 x = 0;
507 while (s[x])
508 {
509 Draw_Character((x + skip) << 3, v, s[x]);
510 x++;
511 }
512 Draw_Character((x + skip) << 3, v,
513 10 + ((int)(realtime * con_cursorspeed) & 1));
514 v += 8;
515 }
516
517 if (v > con_notifylines)
518 con_notifylines = v;
519 }
520
521 static void
Con_DrawDLBar(void)522 Con_DrawDLBar(void)
523 {
524 #ifdef QW_HACK
525 char dlbar[256];
526 const char *dlname;
527 char *buf;
528 int bufspace;
529 int namespace, len;
530 int barchars, totalchars, markpos;
531 int i;
532
533 if (!cls.download)
534 return;
535
536 /*
537 * totalchars = the space available for the whole bar + text
538 * barchars = the space for just the bar
539 *
540 * Bar looks like this:
541 * filename <====*====> 42%
542 * longfilen... <==*======> 31%
543 */
544 dlname = COM_SkipPath(cls.downloadname);
545
546 buf = dlbar;
547 bufspace = sizeof(dlbar) - 1;
548
549 namespace = qmin(con_linewidth / 3, 20);
550 totalchars = qmin(con_linewidth - 2, (int)sizeof(dlbar) - 1);
551
552 if (strlen(dlname) > namespace) {
553 len = snprintf(buf, bufspace, "%.*s... ", namespace - 2, dlname);
554 } else {
555 len = snprintf(buf, bufspace, "%-*s ", namespace + 1, dlname);
556 }
557 buf += len;
558 totalchars -= len;
559 barchars = totalchars - 7; /* 7 => 2 endcaps, space, "100%" */
560
561 markpos = barchars * cls.downloadpercent / 100;
562 *buf++ = '\x80';
563 for (i = 0; i < barchars; i++) {
564 if (i == markpos)
565 *buf++ = '\x83';
566 else
567 *buf++ = '\x81';
568 }
569 *buf++ = '\x82';
570 snprintf(buf, 6, " %3d%%", cls.downloadpercent);
571
572 /* draw it */
573 Draw_String(8, con_vislines - 22 + 8, dlbar);
574 #endif
575 }
576
577 /*
578 ================
579 Con_DrawConsole
580
581 Draws the console with the solid background
582 FIXME - The input line at the bottom should only be drawn if typing is allowed
583 ================
584 */
Con_DrawConsole(int lines)585 void Con_DrawConsole(int lines)
586 {
587 int i, x, y;
588 int rows;
589 char *text;
590 int row;
591
592 if (lines <= 0)
593 return;
594
595 // draw the background
596 Draw_ConsoleBackground(lines);
597
598 // draw the text
599 con_vislines = lines;
600 rows = (lines - 22) >> 3; // rows of text to draw
601 y = lines - 30;
602
603 // draw from the bottom up
604 if (con->display != con->current) {
605 // draw arrows to show the buffer is backscrolled
606 for (x = 0; x < con_linewidth; x += 4)
607 Draw_Character((x + 1) << 3, y, '^');
608 y -= 8;
609 rows--;
610 }
611
612 row = con->display;
613 for (i = 0; i < rows; i++, y -= 8, row--) {
614 if (row < 0)
615 break;
616 if (con->current - row >= con_totallines)
617 break; // past scrollback wrap point
618
619 text = con->text + (row % con_totallines) * con_linewidth;
620 for (x = 0; x < con_linewidth; x++)
621 Draw_Character((x + 1) << 3, y, text[x]);
622 }
623
624 // draw the download bar, if needed
625 Con_DrawDLBar();
626
627 // draw the input prompt, user text, and cursor if desired
628 Con_DrawInput();
629 }
630
631
632 /*
633 ==================
634 Con_NotifyBox
635 ==================
636 */
637 void
Con_NotifyBox(char * text)638 Con_NotifyBox(char *text)
639 {
640 double t1, t2;
641
642 // during startup for sound / cd warnings
643 Con_Printf
644 ("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n");
645
646 Con_Printf("%s", text);
647
648 Con_Printf("Press a key.\n");
649 Con_Printf
650 ("\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\37\n");
651
652 key_count = -2; // wait for a key down and up
653 key_dest = key_console;
654
655 do
656 {
657 t1 = Sys_DoubleTime();
658 SCR_UpdateScreen();
659 Sys_SendKeyEvents();
660 t2 = Sys_DoubleTime();
661 realtime += t2 - t1; // make the cursor blink
662 } while (key_count < 0);
663
664 Con_Printf("\n");
665 key_dest = key_game;
666 realtime = 0; // put the cursor back to invisible
667 }
668
669
670 /*
671 ==================
672 Con_SafePrintf
673
674 Okay to call even when the screen can't be updated
675 ==================
676 */
677 void
Con_SafePrintf(const char * fmt,...)678 Con_SafePrintf(const char *fmt, ...)
679 {
680 va_list argptr;
681 char msg[MAX_PRINTMSG];
682 int temp;
683
684 va_start(argptr, fmt);
685 vsnprintf(msg, sizeof(msg), fmt, argptr);
686 va_end(argptr);
687
688 temp = scr_disabled_for_loading;
689 scr_disabled_for_loading = true;
690 Con_Printf("%s", msg);
691 scr_disabled_for_loading = temp;
692 }
693
694 void
Con_ShowList(const char ** list,int cnt,int maxlen)695 Con_ShowList(const char **list, int cnt, int maxlen)
696 {
697 const char *s;
698 unsigned i, j, len, cols, rows;
699 char *line;
700
701 /* Lay them out in columns */
702 line = (char*)Z_Malloc(Con_GetWidth() + 1);
703 cols = Con_GetWidth() / (maxlen + 2);
704 rows = cnt / cols + ((cnt % cols) ? 1 : 0);
705
706 /* Looks better if we have a few rows before spreading out */
707 if (rows < 5) {
708 cols = cnt / 5 + ((cnt % 5) ? 1 : 0);
709 rows = cnt / cols + ((cnt % cols) ? 1 : 0);
710 }
711
712 for (i = 0; i < rows; ++i) {
713 line[0] = '\0';
714 for (j = 0; j < cols; ++j) {
715 if (j * rows + i >= cnt)
716 break;
717 s = list[j * rows + i];
718 len = strlen(s);
719
720 strcat(line, s);
721 if (j < cols - 1) {
722 while (len < maxlen) {
723 strcat(line, " ");
724 len++;
725 }
726 strcat(line, " ");
727 }
728 }
729 Con_Printf("%s\n", line);
730 }
731 Z_Free(line);
732 }
733
734 static const char **showtree_list;
735 static unsigned showtree_idx;
736
737 static void
Con_ShowTree_Populate(struct rb_node * n)738 Con_ShowTree_Populate(struct rb_node *n)
739 {
740 if (n) {
741 Con_ShowTree_Populate(n->rb_left);
742 showtree_list[showtree_idx++] = stree_entry(n)->string;
743 Con_ShowTree_Populate(n->rb_right);
744 }
745 }
746
747 void
Con_ShowTree(struct stree_root * root)748 Con_ShowTree(struct stree_root *root)
749 {
750 /* FIXME - cheating with malloc */
751 showtree_list = (const char**)malloc(root->entries * sizeof(char *));
752 if (showtree_list) {
753 showtree_idx = 0;
754 Con_ShowTree_Populate(root->root.rb_node);
755 Con_ShowList(showtree_list, root->entries, root->maxlen);
756 free(showtree_list);
757 }
758 }
759
760
761 void
Con_Maplist_f()762 Con_Maplist_f()
763 {
764 struct stree_root st_root;
765 const char *pfx = NULL;
766
767 st_root.entries = 0;
768 st_root.maxlen = 0;
769 st_root.minlen = -1;
770 //st_root.root = NULL;
771 st_root.stack = NULL;
772
773 if (Cmd_Argc() == 2)
774 pfx = Cmd_Argv(1);
775
776 STree_AllocInit();
777 COM_ScanDir(&st_root, "maps", pfx, ".bsp", true);
778 Con_ShowTree(&st_root);
779 }
780
781
782 /*
783 ================
784 Con_Init
785 ================
786 */
787 void
Con_Init(void)788 Con_Init(void)
789 {
790 debuglog = COM_CheckParm("-condebug");
791
792 con_main.text = (char*)Hunk_AllocName(CON_TEXTSIZE, "conmain");
793
794 con = &con_main;
795 con_linewidth = -1;
796 Con_CheckResize();
797
798 Con_Printf("Console initialized.\n");
799
800 /* register our commands */
801 Cvar_RegisterVariable(&con_notifytime);
802
803 Cmd_AddCommand("toggleconsole", Con_ToggleConsole_f);
804 Cmd_AddCommand("messagemode", Con_MessageMode_f);
805 Cmd_AddCommand("messagemode2", Con_MessageMode2_f);
806 Cmd_AddCommand("clear", Con_Clear_f);
807 Cmd_AddCommand("maplist", Con_Maplist_f);
808
809 con_initialized = true;
810 }
811