1 /*
2  * Copyright (C) 1997-2001 Id Software, Inc.
3  *
4  * This program is free software; you can redistribute it and/or modify it under
5  * the terms of the GNU General Public License as published by the Free
6  * Software Foundation; either version 2 of the License, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * 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 along with
16  * this program; if not, write to the Free Software Foundation, Inc., 59
17  * Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18  *
19  */
20 /* console.c */
21 
22 #include "client.h"
23 
24 console_t	con;
25 
26 cvar_t         *con_notify;
27 cvar_t         *con_notifytime;
28 cvar_t         *con_transparency;
29 
30 
31 #define		MAXCMDLINE	256
32 extern char	key_lines[32][MAXCMDLINE];
33 extern int	edit_line;
34 extern int	key_linepos;
35 
36 
37 void
DrawString(int x,int y,char * s)38 DrawString(int x, int y, char *s)
39 {
40 	while (*s) {
41 		re.DrawChar(x, y, *s, 256);
42 		x += 8;
43 		s++;
44 	}
45 }
46 
47 void
DrawAltString(int x,int y,char * s)48 DrawAltString(int x, int y, char *s)
49 {
50 	while (*s) {
51 		re.DrawChar(x, y, *s ^ 0x80, 256);
52 		x += 8;
53 		s++;
54 	}
55 }
56 
57 
58 void
Key_ClearTyping(void)59 Key_ClearTyping(void)
60 {
61 	key_lines[edit_line][1] = 0;	/* clear any typing */
62 	key_linepos = 1;
63 }
64 
65 /*
66  * ================ Con_ToggleConsole_f ================
67  */
68 void
Con_ToggleConsole_f(void)69 Con_ToggleConsole_f(void)
70 {
71 	SCR_EndLoadingPlaque();	/* get rid of loading plaque */
72 
73 	if (cl.attractloop) {
74 		Cbuf_AddText("killserver\n");
75 		return;
76 	}
77 	if (cls.state == ca_disconnected) {	/* start the demo loop again */
78 		Cbuf_AddText("d1\n");
79 		return;
80 	}
81 	Key_ClearTyping();
82 	Con_ClearNotify();
83 
84 	if (cls.key_dest == key_console) {
85 		M_ForceMenuOff();
86 		Cvar_Set("paused", "0");
87 	} else {
88 		M_ForceMenuOff();
89 		cls.key_dest = key_console;
90 
91 		if (Cvar_VariableValue("maxclients") == 1
92 		    && Com_ServerState())
93 			Cvar_Set("paused", "1");
94 	}
95 }
96 
97 /*
98  * ================ Con_ToggleChat_f ================
99  */
100 void
Con_ToggleChat_f(void)101 Con_ToggleChat_f(void)
102 {
103 	Key_ClearTyping();
104 	if (cls.key_dest == key_console)
105 	{
106 		if (cls.state == ca_active) {
107 			M_ForceMenuOff();
108 			cls.key_dest = key_game;
109 		}
110 	} else
111 		cls.key_dest = key_console;
112 	Con_ClearNotify();
113 }
114 
115 /*
116  * ================ Con_Clear_f ================
117  */
118 void
Con_Clear_f(void)119 Con_Clear_f(void)
120 {
121 	memset(con.text, ' ', CON_TEXTSIZE);
122 }
123 
124 
125 /*
126  * ================
127  * Con_Dump_f
128  *
129  * Save the console contents out to a file.
130  * ================
131  */
132 void
Con_Dump_f(void)133 Con_Dump_f(void)
134 {
135 	int		l, x;
136 	char           *line;
137 	FILE           *f;
138 	char		buffer[1024];
139 	char		name[MAX_OSPATH];
140 
141 	if (Cmd_Argc() != 2) {
142 		Com_Printf("usage: condump <filename>\n");
143 		return;
144 	}
145 	Com_sprintf(name, sizeof(name), "%s/%s.txt", FS_Gamedir(), Cmd_Argv(1));
146 
147 	Com_Printf("Dumped console text to %s.\n", name);
148 	FS_CreatePath(name);
149 	f = fopen(name, "w");
150 	if (!f) {
151 		Com_Printf("ERROR: couldn't open.\n");
152 		return;
153 	}
154 	/* skip empty lines */
155 	for (l = con.current - con.totallines + 1; l <= con.current; l++) {
156 		line = con.text + (l % con.totallines) * con.linewidth;
157 		for (x = 0; x < con.linewidth; x++)
158 			if (line[x] != ' ')
159 				break;
160 		if (x != con.linewidth)
161 			break;
162 	}
163 
164 	/* write the remaining lines */
165 	buffer[con.linewidth] = 0;
166 	for (; l <= con.current; l++) {
167 		line = con.text + (l % con.totallines) * con.linewidth;
168 		Q_strncpyz(buffer, line, con.linewidth);
169 		for (x = con.linewidth - 1; x >= 0; x--) {
170 			if (buffer[x] == ' ')
171 				buffer[x] = 0;
172 			else
173 				break;
174 		}
175 		for (x = 0; buffer[x]; x++)
176 			buffer[x] &= 0x7f;
177 
178 		fprintf(f, "%s\n", buffer);
179 	}
180 
181 	fclose(f);
182 }
183 
184 
185 /*
186  * ================ Con_ClearNotify ================
187  */
188 void
Con_ClearNotify(void)189 Con_ClearNotify(void)
190 {
191 	int		i;
192 
193 	for (i = 0; i < NUM_CON_TIMES; i++)
194 		con.times[i] = 0;
195 }
196 
197 
198 /*
199  * ================ Con_MessageMode_f ================
200  */
201 void
Con_MessageMode_f(void)202 Con_MessageMode_f(void)
203 {
204 	chat_team = false;
205 	cls.key_dest = key_message;
206 }
207 
208 /*
209  * ================ Con_MessageMode2_f ================
210  */
211 void
Con_MessageMode2_f(void)212 Con_MessageMode2_f(void)
213 {
214 	chat_team = true;
215 	cls.key_dest = key_message;
216 }
217 
218 /*
219  * ================ Con_CheckResize
220  *
221  * If the line width has changed, reformat the buffer. ================
222  */
223 void
Con_CheckResize(void)224 Con_CheckResize(void)
225 {
226 	int		i         , j, width, oldwidth, oldtotallines, numlines, numchars;
227 	char		tbuf      [CON_TEXTSIZE];
228 
229 	width = (viddef.width >> 3) - 2;
230 
231 	if (width == con.linewidth)
232 		return;
233 
234 	if (width < 1) {	/* video hasn't been initialized yet */
235 		width = 38;
236 		con.linewidth = width;
237 		con.totallines = CON_TEXTSIZE / con.linewidth;
238 		memset(con.text, ' ', CON_TEXTSIZE);
239 	} else {
240 		oldwidth = con.linewidth;
241 		con.linewidth = width;
242 		oldtotallines = con.totallines;
243 		con.totallines = CON_TEXTSIZE / con.linewidth;
244 		numlines = oldtotallines;
245 
246 		if (con.totallines < numlines)
247 			numlines = con.totallines;
248 
249 		numchars = oldwidth;
250 
251 		if (con.linewidth < numchars)
252 			numchars = con.linewidth;
253 
254 		memcpy(tbuf, con.text, CON_TEXTSIZE);
255 		memset(con.text, ' ', CON_TEXTSIZE);
256 
257 		for (i = 0; i < numlines; i++) {
258 			for (j = 0; j < numchars; j++) {
259 				con.text[(con.totallines - 1 - i) * con.linewidth + j] =
260 				    tbuf[((con.current - i + oldtotallines) %
261 				    oldtotallines) * oldwidth + j];
262 			}
263 		}
264 
265 		Con_ClearNotify();
266 	}
267 
268 	con.current = con.totallines - 1;
269 	con.display = con.current;
270 }
271 
272 
273 /*
274  * ================ Con_Init ================
275  */
276 void
Con_Init(void)277 Con_Init(void)
278 {
279 	con.linewidth = -1;
280 
281 	Con_CheckResize();
282 
283 	Com_Printf("Console initialized.\n");
284 
285 	//
286 	/* register our commands */
287 	//
288 	con_notify = Cvar_Get("con_notify", "1", 0);
289 	con_notifytime = Cvar_Get("con_notifytime", "3", 0);
290 	con_transparency = Cvar_Get("con_transparency", "0.5", CVAR_ARCHIVE);
291 
292 	Cmd_AddCommand("toggleconsole", Con_ToggleConsole_f);
293 	Cmd_AddCommand("togglechat", Con_ToggleChat_f);
294 	Cmd_AddCommand("messagemode", Con_MessageMode_f);
295 	Cmd_AddCommand("messagemode2", Con_MessageMode2_f);
296 	Cmd_AddCommand("clear", Con_Clear_f);
297 	Cmd_AddCommand("condump", Con_Dump_f);
298 	con.initialized = true;
299 }
300 
301 
302 /*
303  * =============== Con_Linefeed ===============
304  */
305 void
Con_Linefeed(void)306 Con_Linefeed(void)
307 {
308 	con.x = 0;
309 	if (con.display == con.current)
310 		con.display++;
311 	con.current++;
312 	memset(&con.text[(con.current % con.totallines) * con.linewidth]
313 	    ,' ', con.linewidth);
314 }
315 
316 /*
317  * ================ Con_Print
318  *
319  * Handles cursor positioning, line wrapping, etc All console printing must go
320  * through this in order to be logged to disk If no console is visible, the
321  * text will appear at the top of the game window ================
322  */
323 void
Con_Print(char * txt)324 Con_Print(char *txt)
325 {
326 	int		y;
327 	int		c         , l;
328 	static int	cr;
329 	int		mask;
330 
331 	if (!con.initialized)
332 		return;
333 
334 	if (txt[0] == 1 || txt[0] == 2) {
335 		mask = 128;	/* go to colored text */
336 		txt++;
337 	} else
338 		mask = 0;
339 
340 
341 	while ((c = *txt)) {
342 		/* count word length */
343 		for (l = 0; l < con.linewidth; l++)
344 			if (txt[l] <= ' ')
345 				break;
346 
347 		/* word wrap */
348 		if (l != con.linewidth && (con.x + l > con.linewidth))
349 			con.x = 0;
350 
351 		txt++;
352 
353 		if (cr) {
354 			con.current--;
355 			cr = false;
356 		}
357 		if (!con.x) {
358 			Con_Linefeed();
359 			/* mark time for transparent overlay */
360 			if (con.current >= 0)
361 				con.times[con.current % NUM_CON_TIMES] = cls.realtime;
362 		}
363 		switch (c) {
364 		case '\n':
365 			con.x = 0;
366 			break;
367 
368 		case '\r':
369 			con.x = 0;
370 			cr = 1;
371 			break;
372 
373 		default:	/* display character and advance */
374 			y = con.current % con.totallines;
375 			con.text[y * con.linewidth + con.x] = c | mask | con.ormask;
376 			con.x++;
377 			if (con.x >= con.linewidth)
378 				con.x = 0;
379 			break;
380 		}
381 
382 	}
383 }
384 
385 
386 /*
387  * ============== Con_CenteredPrint ==============
388  */
389 void
Con_CenteredPrint(char * text)390 Con_CenteredPrint(char *text)
391 {
392 	int		l;
393 	char		buffer[1024];
394 
395 	l = strlen(text);
396 	l = (con.linewidth - l) / 2;
397 	if (l < 0)
398 		l = 0;
399 	memset(buffer, ' ', l);
400 	Q_strncpyz(buffer + l, text, sizeof(buffer));
401 	strncat(buffer, "\n", sizeof(buffer) - strlen(buffer) - 1);
402 	Con_Print(buffer);
403 }
404 
405 /*
406  *
407  * ============================================================================
408  *
409  * DRAWING
410  *
411  * ======================================================================
412  */
413 
414 
415 /*
416  * ================
417  * Con_DrawInput
418  *
419  * The input line scrolls horizontally if typing goes beyond the right edge.
420  * ================
421  */
422 void
Con_DrawInput(void)423 Con_DrawInput(void)
424 {
425 	int		y;
426 	int		i;
427 	char           *text;
428 
429 	if (cls.key_dest == key_menu)
430 		return;
431 	if (cls.key_dest != key_console && cls.state == ca_active)
432 		return;		/* don't draw anything (always draw if not active) */
433 	text = key_lines[edit_line];
434 
435 	/* add the cursor frame */
436 	text[key_linepos] = 10 + ((int)(cls.realtime >> 8) & 1);
437 
438 	/* fill out remainder with spaces */
439 	for (i = key_linepos + 1; i < con.linewidth; i++)
440 		text[i] = ' ';
441 
442 	/* prestep if horizontally scrolling */
443 	if (key_linepos >= con.linewidth)
444 		text += 1 + key_linepos - con.linewidth;
445 
446 	/* draw it */
447 	y = con.vislines - 16;
448 
449 	for (i = 0; i < con.linewidth; i++)
450 		re.DrawChar((i + 1) << 3, con.vislines - 22, text[i], 256);
451 
452 	/* remove cursor */
453 	key_lines[edit_line][key_linepos] = 0;
454 }
455 
456 
457 /*
458  * ================ Con_DrawNotify
459  *
460  * Draws the last few lines of output transparently over the game top
461  * ================
462  */
463 void
Con_DrawNotify(void)464 Con_DrawNotify(void)
465 {
466 	int		x         , v;
467 	char           *text;
468 	int		i;
469 	int		time;
470 	char           *s;
471 	int		skip;
472 
473 	v = 0;
474 	for (i = con.current - NUM_CON_TIMES + 1; i <= con.current; i++) {
475 		if (i < 0)
476 			continue;
477 		time = con.times[i % NUM_CON_TIMES];
478 		if (time == 0)
479 			continue;
480 		time = cls.realtime - time;
481 		if (time > con_notifytime->value * 1000)
482 			continue;
483 		text = con.text + (i % con.totallines) * con.linewidth;
484 
485 		for (x = 0; x < con.linewidth; x++)
486 			if (con_notify->value)
487 				re.DrawChar((x + 1) << 3, v, text[x], 256);
488 				v += 8;
489 	}
490 
491 
492 	if (cls.key_dest == key_message) {
493 		if (chat_team) {
494 			DrawString(8, v, "say_team:");
495 			skip = 11;
496 		} else {
497 			DrawString(8, v, "say:");
498 			skip = 5;
499 		}
500 
501 		s = chat_buffer;
502 		if (chat_bufferlen > (viddef.width >> 3) - (skip + 1))
503 			s += chat_bufferlen - ((viddef.width >> 3) - (skip + 1));
504 		x = 0;
505 		while (s[x]) {
506 			re.DrawChar((x + skip) << 3, v, s[x], 256);
507 			x++;
508 		}
509 		re.DrawChar((x + skip) << 3, v, 10 + ((cls.realtime >> 8) & 1), 256);
510 		v += 8;
511 	}
512 	if (v) {
513 		SCR_AddDirtyPoint(0, 0);
514 		SCR_AddDirtyPoint(viddef.width - 1, v);
515 	}
516 }
517 
518 /*
519  * ================ Con_DrawConsole
520  *
521  * Draws the console with the solid background ================
522  */
523 void
Con_DrawConsole(float frac,qboolean in_game)524 Con_DrawConsole(float frac, qboolean in_game)
525 {
526 	int		i, j, x, y, n;
527 	int		rows;
528 	char           *text;
529 	int		row;
530 	int		lines;
531 	char		version[64];
532 	char		dlbar[1024];
533 	float		alpha;
534 	char           *conback_image;
535 	int		length;
536 
537 	struct tm      *ntime;
538 	char		stime[32];
539 	time_t		l_time;
540 
541 	lines = viddef.height * frac;
542 	if (lines <= 0)
543 		return;
544 
545 	if (lines > viddef.height)
546 		lines = viddef.height;
547 
548 	if (!in_game)
549 		alpha = 1;
550 	else
551 		alpha = con_transparency->value;
552 
553 	/* draw the background */
554 
555 	conback_image = cl_conback_image->string;
556 
557 	re.DrawStretchPic(0, -viddef.height + lines, viddef.width, viddef.height, conback_image, alpha);
558 	SCR_AddDirtyPoint(0, 0);
559 	SCR_AddDirtyPoint(viddef.width - 1, lines - 1);
560 
561 	Com_sprintf(version, sizeof(version), "%s", QUDOS_VERSION);
562 	length = strlen(QUDOS_VERSION);
563 	for (x = 0; x < length; x++)
564 		re.DrawChar(viddef.width - 4 -length * 8 + x * 8, lines - 12, 128 + QUDOS_VERSION[x], 256);
565 
566 	time(&l_time);
567 	ntime = localtime(&l_time);
568 	strftime(stime, sizeof(stime), "%a, %d %b %Y - %H:%M:%S", ntime);
569 	for (x = 0; x < 50; ++x)
570 		re.DrawChar(viddef.width - 215 + x * 8, 0, 128 + stime[x], 256);
571 
572 	/* draw the text */
573 	con.vislines = lines;
574 	rows = (lines - 22) >> 3;	/* rows of text to draw */
575 
576 	y = lines - 30;
577 
578 	/* draw from the bottom up */
579 	if (con.display != con.current) {
580 		/* draw arrows to show the buffer is backscrolled */
581 		for (x = 0; x < con.linewidth; x += 4)
582 			re.DrawChar((x + 1) << 3, y, '^', 256);
583 		y -= 8;
584 		rows--;
585 	}
586 	row = con.display;
587 	for (i = 0; i < rows; i++, y -= 8, row--) {
588 		if (row < 0)
589 			break;
590 		if (con.current - row >= con.totallines)
591 			break;	/* past scrollback wrap point */
592 
593 		text = con.text + (row % con.totallines) * con.linewidth;
594 
595 		for (x = 0; x < con.linewidth; x++)
596 			re.DrawChar((x + 1) << 3, y, text[x], 256);
597 	}
598 
599 	/* ZOID */
600 	/* draw the download bar */
601 	/* figure out width */
602 	if (cls.download) {
603 		if ((text = strrchr(cls.downloadname, '/')) != NULL)
604 			text++;
605 		else
606 			text = cls.downloadname;
607 
608 		x = con.linewidth - ((con.linewidth * 7) / 40);
609 		y = x - strlen(text) - 8;
610 		i = con.linewidth / 3;
611 		if (strlen(text) > i) {
612 			y = x - i - 11;
613 			Q_strncpyz(dlbar, text, i);
614 			strncat(dlbar, "...", sizeof(dlbar) - strlen(dlbar) - 1);
615 		} else
616 			Q_strncpyz(dlbar, text, sizeof(dlbar));
617 		strncat(dlbar, ": ", sizeof(dlbar) - strlen(dlbar) - 1);
618 		i = strlen(dlbar);
619 		dlbar[i++] = '\x80';
620 		/* where's the dot go? */
621 		if (cls.downloadpercent == 0)
622 			n = 0;
623 		else
624 			n = y * cls.downloadpercent / 100;
625 
626 		for (j = 0; j < y; j++)
627 			if (j == n)
628 				dlbar[i++] = '\x83';
629 			else
630 				dlbar[i++] = '\x81';
631 		dlbar[i++] = '\x82';
632 		dlbar[i] = 0;
633 
634 		Com_sprintf(dlbar + strlen(dlbar), sizeof(dlbar) - strlen(dlbar), " %02d%%", cls.downloadpercent);
635 
636 		/* draw it */
637 		y = con.vislines - 12;
638 		for (i = 0; i < strlen(dlbar); i++)
639 			re.DrawChar((i + 1) << 3, y, dlbar[i], 256);
640 	}
641 	/* ZOID */
642 
643 	/* draw the input prompt, user text, and cursor if desired */
644 	Con_DrawInput();
645 }
646