1 /*
2 Copyright (C) 1997-2001 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 "client.h"
23 #include <time.h>
24 
25 console_t	con;
26 
27 cvar_t		*con_notifytime;
28 
29 extern	char	key_lines[32][MAXCMDLINE];
30 extern	int		edit_line;
31 extern	int		key_linepos;
32 
33 
DrawString(int x,int y,const char * s)34 void DrawString (int x, int y, const char *s)
35 {
36 	//r1: don't draw if obscured
37 	if (viddef.height * scr_conlines > y)
38 		return;
39 
40 	while (*s)
41 	{
42 		re.DrawChar (x, y, *s);
43 		x+=8;
44 		s++;
45 	}
46 }
47 
DrawAltString(int x,int y,const char * s)48 void DrawAltString (int x, int y, const char *s)
49 {
50 	//r1: don't draw if obscured
51 	if (viddef.height * scr_conlines > y)
52 		return;
53 
54 	while (*s)
55 	{
56 		re.DrawChar (x, y, *s ^ 0x80);
57 		x+=8;
58 		s++;
59 	}
60 }
61 
62 
Key_ClearTyping(void)63 void Key_ClearTyping (void)
64 {
65 	key_lines[edit_line][1] = 0;	// clear any typing
66 	key_linepos = 1;
67 }
68 
69 /*
70 ================
71 Con_ToggleConsole_f
72 ================
73 */
Con_ToggleConsole_f(void)74 void Con_ToggleConsole_f (void)
75 {
76 	SCR_EndLoadingPlaque ();	// get rid of loading plaque
77 
78 	//r1: fucking annoying.
79 	/*if (cl.attractloop)
80 	{
81 		cl.attractloop = false;
82 		Cmd_ExecuteString ("disconnect\n");
83 		return;
84 	}*/
85 
86 	if (cls.state == ca_disconnected)
87 	{	// start the demo loop again
88 
89 		//r1: fucking annoying.
90 		//Cbuf_AddText ("d1\n");
91 		cls.key_dest = key_console;
92 		return;
93 	}
94 
95 	Key_ClearTyping ();
96 	Con_ClearNotify ();
97 
98 	if (cls.key_dest == key_console)
99 	{
100 		M_ForceMenuOff ();
101 		//if (chat_bufferlen)
102 		//	cls.key_dest = key_message;
103 		//Cvar_Set ("paused", "0");
104 	}
105 	else
106 	{
107 		M_ForceMenuOff ();
108 		cls.key_dest = key_console;
109 
110 		/*if (Cvar_VariableValue ("maxclients") == 1
111 			&& Com_ServerState ())
112 			Cvar_Set ("paused", "1");*/
113 	}
114 }
115 
116 /*
117 ================
118 Con_ToggleChat_f
119 ================
120 */
Con_ToggleChat_f(void)121 void Con_ToggleChat_f (void)
122 {
123 	Key_ClearTyping ();
124 
125 	if (cls.key_dest == key_console)
126 	{
127 		if (cls.state == ca_active)
128 		{
129 			M_ForceMenuOff ();
130 			cls.key_dest = key_game;
131 		}
132 	}
133 	else
134 		cls.key_dest = key_console;
135 
136 	Con_ClearNotify ();
137 }
138 
139 /*
140 ================
141 Con_Clear_f
142 ================
143 */
Con_Clear_f(void)144 static void Con_Clear_f (void)
145 {
146 	memset (con.text, ' ', sizeof(con.text));
147 }
148 
149 
150 /*
151 ================
152 Con_Dump_f
153 
154 Save the console contents out to a file
155 ================
156 */
Con_Dump_f(void)157 static void Con_Dump_f (void)
158 {
159 	int		l, x;
160 	char	*line;
161 	FILE	*f;
162 	char	buffer[1024];
163 	char	name[MAX_OSPATH];
164 
165 	if (Cmd_Argc() != 2)
166 	{
167 		Com_Printf ("usage: condump <filename>\n", LOG_CLIENT);
168 		return;
169 	}
170 
171 	//r1: consolidate condumps to their own dir
172 	Com_sprintf (name, sizeof(name), "%s/condumps/%s.txt", FS_Gamedir(), Cmd_Argv(1));
173 
174 	if (strstr (Cmd_Argv(1), "..") || strchr (Cmd_Argv(1), '/') || strchr (Cmd_Argv(1), '\\') )
175 	{
176 		Com_Printf ("Illegal filename.\n", LOG_CLIENT);
177 		return;
178 	}
179 
180 	FS_CreatePath (name);
181 	f = fopen (name, "w");
182 	if (!f)
183 	{
184 		Com_Printf ("ERROR: couldn't open.\n", LOG_CLIENT);
185 		return;
186 	}
187 
188 	//r1: save some basic infos
189 	if (cls.state == ca_active) {
190 		time_t timet;
191 		time(&timet);
192 		fprintf (f, "%s on server %s, map %s.\n-----------------\n", ctime (&timet), cls.servername, cl.configstrings[CS_MODELS+1] + 5);
193 	}
194 
195 	// skip empty lines
196 	for (l = con.current - con.totallines + 1 ; l <= con.current ; l++)
197 	{
198 		line = con.text + (l%con.totallines)*con.linewidth;
199 		for (x=0 ; x<con.linewidth ; x++)
200 			if (line[x] != ' ')
201 				break;
202 		if (x != con.linewidth)
203 			break;
204 	}
205 
206 	// write the remaining lines
207 	buffer[con.linewidth] = 0;
208 	for ( ; l <= con.current ; l++)
209 	{
210 		line = con.text + (l%con.totallines)*con.linewidth;
211 		strncpy (buffer, line, con.linewidth);
212 		for (x=con.linewidth-1 ; x>=0 ; x--)
213 		{
214 			if (buffer[x] == ' ')
215 				buffer[x] = 0;
216 			else
217 				break;
218 		}
219 		for (x=0; buffer[x]; x++)
220 			buffer[x] &= 0x7f;
221 
222 		fprintf (f, "%s\n", buffer);
223 	}
224 
225 	Com_Printf ("Dumped console text to %s.\n", LOG_CLIENT, name);
226 
227 	fclose (f);
228 }
229 
230 
231 /*
232 ================
233 Con_ClearNotify
234 ================
235 */
Con_ClearNotify(void)236 void Con_ClearNotify (void)
237 {
238 	/*int		i;
239 
240 	for (i=0 ; i<NUM_CON_TIMES ; i++)
241 		con.times[i] = 0;*/
242 	memset (&con.times, 0, sizeof (con.times));
243 }
244 
245 
246 /*
247 ================
248 Con_MessageMode_f
249 ================
250 */
Con_MessageMode_f(void)251 static void Con_MessageMode_f (void)
252 {
253 	if (cls.state != ca_active)
254 		return;
255 
256 	chat_mode = CHAT_MODE_PUBLIC;
257 
258 	if (!chat_bufferlen && Cmd_Argc() > 1)
259 	{
260 		Q_strncpy (chat_buffer[chat_curbuffer], Cmd_Args(), sizeof(chat_buffer[chat_curbuffer])-2);
261 		if (chat_buffer[chat_curbuffer][0])
262 		{
263 			strcat (chat_buffer[chat_curbuffer], " ");
264 			chat_bufferlen = (int)strlen(chat_buffer[chat_curbuffer]);
265 			chat_cursorpos = chat_bufferlen;
266 		}
267 	}
268 
269 	cls.key_dest = key_message;
270 }
271 
272 /*
273 ================
274 Con_MessageMode2_f
275 ================
276 */
Con_MessageMode2_f(void)277 static void Con_MessageMode2_f (void)
278 {
279 	if (cls.state != ca_active)
280 		return;
281 
282 	chat_mode = CHAT_MODE_TEAM;
283 
284 	if (!chat_bufferlen && Cmd_Argc() > 1)
285 	{
286 		Q_strncpy (chat_buffer[chat_curbuffer], Cmd_Args(), sizeof(chat_buffer[chat_curbuffer])-2);
287 		if (chat_buffer[chat_curbuffer][0])
288 		{
289 			strcat (chat_buffer[chat_curbuffer], " ");
290 			chat_bufferlen = (int)strlen(chat_buffer[chat_curbuffer]);
291 			chat_cursorpos = chat_bufferlen;
292 		}
293 	}
294 
295 	cls.key_dest = key_message;
296 }
297 
Con_MessageModex_f(void)298 static void Con_MessageModex_f (void)
299 {
300 	if (cls.state != ca_active)
301 		return;
302 
303 	if (Cmd_Argc() < 2)
304 	{
305 		Com_Printf ("Usage: messagemodex prompt [default]\n", LOG_GENERAL);
306 		return;
307 	}
308 
309 	chat_mode = CHAT_MODE_CUSTOM;
310 
311 	strncpy (chat_custom_prompt, Cmd_Argv(1), sizeof(chat_custom_prompt)-3);
312 	strcpy (chat_custom_cmd, chat_custom_prompt);
313 	strcat (chat_custom_prompt, ": ");
314 
315 	if (!chat_bufferlen && Cmd_Argc() > 2)
316 	{
317 		Q_strncpy (chat_buffer[chat_curbuffer], Cmd_Args2(2), sizeof(chat_buffer[chat_curbuffer])-2);
318 		if (chat_buffer[chat_curbuffer][0])
319 		{
320 			strcat (chat_buffer[chat_curbuffer], " ");
321 			chat_bufferlen = (int)strlen(chat_buffer[chat_curbuffer]);
322 			chat_cursorpos = chat_bufferlen;
323 		}
324 	}
325 
326 	cls.key_dest = key_message;
327 }
328 
Con_Resize(int width)329 static void Con_Resize (int width)
330 {
331 	char	tbuf[CON_TEXTSIZE];
332 	int		i, j, oldwidth, oldtotallines, numlines, numchars;
333 
334 	oldwidth = con.linewidth;
335 	con.linewidth = width;
336 	oldtotallines = con.totallines;
337 	con.totallines = CON_TEXTSIZE / con.linewidth;
338 	numlines = oldtotallines;
339 
340 	if (con.totallines < numlines)
341 		numlines = con.totallines;
342 
343 	numchars = oldwidth;
344 
345 	if (con.linewidth < numchars)
346 		numchars = con.linewidth;
347 
348 	memcpy (tbuf, con.text, CON_TEXTSIZE);
349 	memset (con.text, ' ', CON_TEXTSIZE);
350 
351 	for (i=0 ; i<numlines ; i++)
352 	{
353 		for (j=0 ; j<numchars ; j++)
354 		{
355 			con.text[(con.totallines - 1 - i) * con.linewidth + j] =
356 					tbuf[((con.current - i + oldtotallines) %
357 							oldtotallines) * oldwidth + j];
358 		}
359 	}
360 }
361 /*
362 ================
363 Con_CheckResize
364 
365 If the line width has changed, reformat the buffer.
366 ================
367 */
Con_CheckResize(void)368 void Con_CheckResize (void)
369 {
370 	int		width;
371 
372 	width = (viddef.width >> 3) - 2;
373 
374 	if (width == con.linewidth)
375 		return;
376 
377 	if (width < 1)			// video hasn't been initialized yet
378 	{
379 		width = 38;
380 		con.linewidth = width;
381 		con.totallines = sizeof(con.text) / con.linewidth;
382 		memset (con.text, ' ', sizeof(con.text));
383 	}
384 	else
385 	{
386 		Con_Resize (width);
387 		Con_ClearNotify ();
388 	}
389 
390 	con.current = con.totallines - 1;
391 	con.display = con.current;
392 }
393 
394 
395 /*
396 ================
397 Con_Init
398 ================
399 */
Con_Init(void)400 void Con_Init (void)
401 {
402 	con.linewidth = -1;
403 
404 	Con_CheckResize ();
405 
406 	Com_Printf ("Console initialized.\n", LOG_CLIENT);
407 
408 	cls.key_dest = key_console;
409 
410 //
411 // register our commands
412 //
413 	con_notifytime = Cvar_Get ("con_notifytime", "3", 0);
414 
415 	Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
416 	Cmd_AddCommand ("togglechat", Con_ToggleChat_f);
417 	Cmd_AddCommand ("messagemode", Con_MessageMode_f);
418 	Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
419 	Cmd_AddCommand ("messagemodex", Con_MessageModex_f);
420 	Cmd_AddCommand ("clear", Con_Clear_f);
421 	Cmd_AddCommand ("condump", Con_Dump_f);
422 	con.initialized = true;
423 }
424 
425 
426 /*
427 ===============
428 Con_Linefeed
429 ===============
430 */
Con_Linefeed(void)431 static void Con_Linefeed (void)
432 {
433 	con.x = 0;
434 	if (con.display == con.current)
435 		con.display++;
436 	con.current++;
437 	memset (&con.text[(con.current%con.totallines)*con.linewidth]
438 	, ' ', con.linewidth);
439 }
440 
441 /*
442 ================
443 Con_Print
444 
445 Handles cursor positioning, line wrapping, etc
446 All console printing must go through this in order to be logged to disk
447 If no console is visible, the text will appear at the top of the game window
448 ================
449 */
Con_Print(const char * txt)450 void Con_Print (const char *txt)
451 {
452 	int		y;
453 	int		c, l;
454 	static int	cr;
455 	int		mask;
456 
457 	if (!con.initialized)
458 		return;
459 
460 	if (txt[0] == 1 || txt[0] == 2)
461 	{
462 		mask = 128;		// go to colored text
463 		txt++;
464 	}
465 	else
466 		mask = 0;
467 
468 
469 	while ( (c = *txt) )
470 	{
471 	// count word length
472 		for (l=0 ; l< con.linewidth ; l++)
473 			if ( txt[l] <= ' ')
474 				break;
475 
476 	// word wrap
477 		if (l != con.linewidth && (con.x + l > con.linewidth) )
478 			con.x = 0;
479 
480 		txt++;
481 
482 		if (cr)
483 		{
484 			con.current--;
485 			cr = false;
486 		}
487 
488 
489 		if (!con.x)
490 		{
491 			Con_Linefeed ();
492 		// mark time for transparent overlay
493 			if (con.current >= 0)
494 				con.times[con.current % NUM_CON_TIMES] = cls.realtime;
495 		}
496 
497 		switch (c)
498 		{
499 		case '\n':
500 			con.x = 0;
501 			break;
502 
503 		case '\r':
504 			con.x = 0;
505 			cr = 1;
506 			break;
507 
508 		default:	// display character and advance
509 			y = con.current % con.totallines;
510 			con.text[y*con.linewidth+con.x] = c | mask | con.ormask;
511 			con.x++;
512 			if (con.x >= con.linewidth)
513 				con.x = 0;
514 			break;
515 		}
516 
517 	}
518 }
519 
520 
521 /*
522 ==============
523 Con_CenteredPrint
524 ==============
525 */
526 /*void Con_CenteredPrint (char *text)
527 {
528 	int		l;
529 	char	buffer[1024];
530 
531 	l = strlen(text);
532 	l = (con.linewidth-l)/2;
533 	if (l < 0)
534 		l = 0;
535 	memset (buffer, ' ', l);
536 	strcpy (buffer+l, text);
537 	strcat (buffer, "\n");
538 	Con_Print (buffer);
539 }*/
540 
541 /*
542 ==============================================================================
543 
544 DRAWING
545 
546 ==============================================================================
547 */
548 
549 
550 /*
551 ================
552 Con_DrawInput
553 
554 The input line scrolls horizontally if typing goes beyond the right edge
555 ================
556 */
Con_DrawInput(void)557 static void Con_DrawInput (void)
558 {
559 	int		linepos;
560 	int		length;
561 	int		i;
562 	char	*text;
563 
564 	if (cls.key_dest == key_menu)
565 		return;
566 	if (cls.key_dest != key_console && cls.state == ca_active)
567 		return;		// don't draw anything (always draw if not active)
568 
569 	text = key_lines[edit_line];
570 
571 // add the cursor frame
572 	//text[key_linepos] = 10+((int)(cls.realtime>>8)&1);
573 
574 // fill out remainder with spaces
575 	//for (i=key_linepos+1 ; i< con.linewidth ; i++)
576 	//	text[i] = ' ';
577 
578 //	prestep if horizontally scrolling
579 	if (key_linepos + 1  >= con.linewidth)
580 	{
581 		linepos = con.linewidth;
582 		text += 1 + key_linepos - con.linewidth;
583 	}
584 	else
585 	{
586 		linepos = key_linepos + 1;
587 	}
588 
589 // draw it
590 	//y = con.vislines-16;
591 
592 	length = (int)strlen (text);
593 
594 	for (i=0 ; i<length; i++)
595 		re.DrawChar ( (i+1)<<3, con.vislines - 22, text[i]);
596 
597 	if (((int)(cls.realtime>>8)&1))
598 		re.DrawChar ( (linepos)<<3, con.vislines - 21, '_');
599 
600 // remove cursor
601 	//key_lines[edit_line][key_linepos] = 0;
602 }
603 
604 
605 /*
606 ================
607 Con_DrawNotify
608 
609 Draws the last few lines of output transparently over the game top
610 ================
611 */
Con_DrawNotify(void)612 void Con_DrawNotify (void)
613 {
614 	int		x, v;
615 	const char	*text;
616 	int		i;
617 	int		time;
618 	char	*s;
619 
620 	v = 0;
621 	for (i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++)
622 	{
623 		if (i < 0)
624 			continue;
625 		time = con.times[i % NUM_CON_TIMES];
626 		if (time == 0)
627 			continue;
628 		time = cls.realtime - time;
629 		if (time > con_notifytime->intvalue*1000)
630 			continue;
631 		text = con.text + (i % con.totallines)*con.linewidth;
632 
633 		for (x = 0 ; x < con.linewidth ; x++)
634 			re.DrawChar ( (x+1)<<3, v, text[x]);
635 
636 		v += 8;
637 	}
638 
639 
640 	if (cls.key_dest == key_message)
641 	{
642 		int		cursorpos;
643 		int		maxwidth;
644 		int		skip = 0;
645 
646 		switch (chat_mode)
647 		{
648 			case CHAT_MODE_PUBLIC:
649 				DrawString (8, v, "say:");
650 				skip = 5;
651 				break;
652 			case CHAT_MODE_TEAM:
653 				DrawString (8, v, "say_team:");
654 				skip = 11;
655 				break;
656 			case CHAT_MODE_CUSTOM:
657 				DrawString (8, v, chat_custom_prompt);
658 				skip = (int)strlen (chat_custom_prompt)+1;
659 				break;
660 		}
661 
662 		s = chat_buffer[chat_curbuffer];
663 
664 		maxwidth =  (viddef.width>>3);
665 
666 		if (chat_cursorpos > maxwidth-(skip+1))
667 		{
668 			s += chat_cursorpos - (maxwidth-(skip+1));
669 			cursorpos = maxwidth-1;
670 		}
671 		else
672 		{
673 			cursorpos = chat_cursorpos + skip;
674 		}
675 
676 		x = 0;
677 		while(s[x])
678 		{
679 			re.DrawChar ( (x+skip)<<3, v, s[x]);
680 			x++;
681 		}
682 
683 		if (((cls.realtime>>8)&1))
684 			re.DrawChar ( (cursorpos)<<3, v+1, '_');
685 		v += 8;
686 	}
687 
688 	if (v)
689 	{
690 		SCR_AddDirtyPoint (0,0);
691 		SCR_AddDirtyPoint (viddef.width-1, v);
692 	}
693 }
694 
695 /*
696 ================
697 Con_DrawConsole
698 
699 Draws the console with the solid background
700 ================
701 */
Con_DrawConsole(float frac)702 void Con_DrawConsole (float frac)
703 {
704 	int				i, j, x, y, n, len, offset;
705 	int				rows;
706 	char			*text;
707 	int				row;
708 	int				lines;
709 	char			version[24];
710 	char			dlbar[1024];
711 
712 	time_t			t;
713 	struct tm		*today;
714 
715 	lines = (int)(viddef.height * frac);
716 
717 	if (lines <= 0)
718 		return;
719 
720 	if (lines > viddef.height)
721 		lines = viddef.height;
722 
723 // draw the background
724 	re.DrawStretchPic (0, lines - viddef.height, viddef.width, viddef.height, "conback");
725 	SCR_AddDirtyPoint (0,0);
726 	SCR_AddDirtyPoint (viddef.width-1,lines-1);
727 
728 	len = (int)strlen(key_lines[edit_line]);
729 
730 	i = Com_sprintf (version, sizeof(version), PRODUCTNAMELOWER " " VERSION);
731 
732 	if (len >= (viddef.width * 0.125f) - (i+2))
733 		offset = 20;
734 	else
735 		offset = 0;
736 
737 	for (x=i-1; x>=0 ; x--)
738 		re.DrawChar (viddef.width-2-(i*8)+x*8, lines-12-offset, 128 + version[x] );
739 
740 	t = time (NULL);
741 	today = localtime(&t);
742 
743 	i = (int)strftime (version, sizeof(version), "%H:%M:%S", today);
744 	for (x=0 ; x<i ; x++)
745 		re.DrawChar (viddef.width-66+x*8, lines-22-offset, 128 + version[x] );
746 
747 // draw the text
748 	con.vislines = lines;
749 
750 #if 0
751 	rows = (lines-8)>>3;		// rows of text to draw
752 
753 	y = lines - 24;
754 #else
755 	rows = (lines-22)>>3;		// rows of text to draw
756 
757 	y = lines - 30;
758 #endif
759 
760 // draw from the bottom up
761 	if (con.display != con.current)
762 	{
763 	// draw arrows to show the buffer is backscrolled
764 		for (x=0 ; x<con.linewidth ; x+=4)
765 			re.DrawChar ( (x+1)<<3, y, '^');
766 
767 		y -= 8;
768 		rows--;
769 	}
770 
771 	row = con.display;
772 	for (i=0 ; i<rows ; i++, y-=8, row--)
773 	{
774 		if (row < 0)
775 			break;
776 		if (con.current - row >= con.totallines)
777 			break;		// past scrollback wrap point
778 
779 		text = con.text + (row % con.totallines)*con.linewidth;
780 
781 		for (x=0 ; x<con.linewidth ; x++)
782 			re.DrawChar ( (x+1)<<3, y, text[x]);
783 	}
784 
785 //ZOID
786 	// draw the download bar
787 	// figure out width
788 	if (cls.downloadname[0] && (cls.download || cls.downloadposition))
789 	{
790 		if ((text = strrchr(cls.downloadname, '/')) != NULL)
791 			text++;
792 		else
793 			text = cls.downloadname;
794 
795 		x = con.linewidth - ((con.linewidth * 7) / 40);
796 		y = x - (int)strlen(text) - 20;
797 		i = con.linewidth/3;
798 		if (strlen(text) > i) {
799 			y = x - i - 11;
800 			Q_strncpy(dlbar, text, i);
801 			strcat(dlbar, "...");
802 		} else
803 			strcpy(dlbar, text);
804 		strcat(dlbar, ": ");
805 		i = (int)strlen(dlbar);
806 		dlbar[i++] = '\x80';
807 		// where's the dot go?
808 		if (cls.downloadpercent == 0)
809 			n = 0;
810 		else
811 			n = y * cls.downloadpercent / 100;
812 
813 		for (j = 0; j < y; j++)
814 			if (j == n)
815 				dlbar[i++] = '\x83';
816 			else
817 				dlbar[i++] = '\x81';
818 		dlbar[i++] = '\x82';
819 
820 		if (cls.download)
821 			cls.downloadposition = ftell (cls.download);
822 
823 		sprintf (dlbar + i, " %02d%% (%.02f KB)", cls.downloadpercent, (float)cls.downloadposition / 1024.0);
824 
825 		j = (int)strlen(dlbar);
826 
827 		// draw it
828 		y = con.vislines-12;
829 		for (i = 0; i < j; i++)
830 			re.DrawChar ( (i+1)<<3, y, dlbar[i]);
831 	}
832 //ZOID
833 
834 // draw the input prompt, user text, and cursor if desired
835 	Con_DrawInput ();
836 }
837 
838 
839