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