1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 
5 This file is part of Quake III Arena source code.
6 
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11 
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 ===========================================================================
21 */
22 // console.c
23 
24 #include "client.h"
25 
26 
27 int g_console_field_width = 78;
28 
29 
30 #define	NUM_CON_TIMES 4
31 
32 #define		CON_TEXTSIZE	32768
33 typedef struct {
34 	qboolean	initialized;
35 
36 	short	text[CON_TEXTSIZE];
37 	int		current;		// line where next message will be printed
38 	int		x;				// offset in current line for next print
39 	int		display;		// bottom of console displays this line
40 
41 	int 	linewidth;		// characters across screen
42 	int		totallines;		// total lines in console scrollback
43 
44 	float	xadjust;		// for wide aspect screens
45 
46 	float	displayFrac;	// aproaches finalFrac at scr_conspeed
47 	float	finalFrac;		// 0.0 to 1.0 lines of console to display
48 	float	userFrac;		// 0.0 to 1.0 - for user Configurations. Don't want to mess with finalFrac - marky
49 	int		vislines;		// in scanlines
50 
51 	int		times[NUM_CON_TIMES];	// cls.realtime time the line was generated
52 								// for transparent notify lines
53 	vec4_t	color;
54 } console_t;
55 
56 extern	console_t	con;
57 
58 console_t	con;
59 
60 cvar_t		*con_conspeed;
61 cvar_t		*con_notifytime;
62 
63 #define	DEFAULT_CONSOLE_WIDTH	78
64 
65 vec4_t	console_color = {1.0, 1.0, 1.0, 1.0};
66 
67 
68 /*
69 ================
70 Con_ToggleConsole_f
71 ================
72 */
Con_ToggleConsole_f(void)73 void Con_ToggleConsole_f (void) {
74 	// Can't toggle the console when it's the only thing available
75 	if ( cls.state == CA_DISCONNECTED && Key_GetCatcher( ) == KEYCATCH_CONSOLE ) {
76 		return;
77 	}
78 
79 	Field_Clear( &g_consoleField );
80 	g_consoleField.widthInChars = g_console_field_width;
81 
82 	Con_ClearNotify ();
83 	Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_CONSOLE );
84 }
85 
86 /*
87 ================
88 Con_MessageMode_f
89 ================
90 */
Con_MessageMode_f(void)91 void Con_MessageMode_f (void) {
92 	chat_playerNum = -1;
93 	chat_team = qfalse;
94 	Field_Clear( &chatField );
95 	chatField.widthInChars = 30;
96 
97 	Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE );
98 }
99 
100 /*
101 ================
102 Con_MessageMode2_f
103 ================
104 */
Con_MessageMode2_f(void)105 void Con_MessageMode2_f (void) {
106 	chat_playerNum = -1;
107 	chat_team = qtrue;
108 	Field_Clear( &chatField );
109 	chatField.widthInChars = 25;
110 	Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE );
111 }
112 
113 /*
114 ================
115 Con_MessageMode3_f
116 ================
117 */
Con_MessageMode3_f(void)118 void Con_MessageMode3_f (void) {
119 	chat_playerNum = VM_Call( cgvm, CG_CROSSHAIR_PLAYER );
120 	if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) {
121 		chat_playerNum = -1;
122 		return;
123 	}
124 	chat_team = qfalse;
125 	Field_Clear( &chatField );
126 	chatField.widthInChars = 30;
127 	Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE );
128 }
129 
130 /*
131 ================
132 Con_MessageMode4_f
133 ================
134 */
Con_MessageMode4_f(void)135 void Con_MessageMode4_f (void) {
136 	chat_playerNum = VM_Call( cgvm, CG_LAST_ATTACKER );
137 	if ( chat_playerNum < 0 || chat_playerNum >= MAX_CLIENTS ) {
138 		chat_playerNum = -1;
139 		return;
140 	}
141 	chat_team = qfalse;
142 	Field_Clear( &chatField );
143 	chatField.widthInChars = 30;
144 	Key_SetCatcher( Key_GetCatcher( ) ^ KEYCATCH_MESSAGE );
145 }
146 
147 /*
148 ================
149 Con_Clear_f
150 ================
151 */
Con_Clear_f(void)152 void Con_Clear_f (void) {
153 	int		i;
154 
155 	for ( i = 0 ; i < CON_TEXTSIZE ; i++ ) {
156 		con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' ';
157 	}
158 
159 	Con_Bottom();		// go to end
160 }
161 
162 
163 /*
164 ================
165 Con_Dump_f
166 
167 Save the console contents out to a file
168 ================
169 */
Con_Dump_f(void)170 void Con_Dump_f (void)
171 {
172 	int		l, x, i;
173 	short	*line;
174 	fileHandle_t	f;
175 	int		bufferlen;
176 	char	*buffer;
177 	char	filename[MAX_QPATH];
178 
179 	if (Cmd_Argc() != 2)
180 	{
181 		Com_Printf ("usage: condump <filename>\n");
182 		return;
183 	}
184 
185 	Q_strncpyz( filename, Cmd_Argv( 1 ), sizeof( filename ) );
186 	COM_DefaultExtension( filename, sizeof( filename ), ".txt" );
187 
188 	if (!COM_CompareExtension(filename, ".txt"))
189 	{
190 		Com_Printf("Con_Dump_f: Only the \".txt\" extension is supported by this command!\n");
191 		return;
192 	}
193 
194 	f = FS_FOpenFileWrite( filename );
195 	if (!f)
196 	{
197 		Com_Printf ("ERROR: couldn't open %s.\n", filename);
198 		return;
199 	}
200 
201 	Com_Printf ("Dumped console text to %s.\n", filename );
202 
203 	// skip empty lines
204 	for (l = con.current - con.totallines + 1 ; l <= con.current ; l++)
205 	{
206 		line = con.text + (l%con.totallines)*con.linewidth;
207 		for (x=0 ; x<con.linewidth ; x++)
208 			if ((line[x] & 0xff) != ' ')
209 				break;
210 		if (x != con.linewidth)
211 			break;
212 	}
213 
214 	// write the remaining lines
215 	buffer[con.linewidth] = 0;
216 	for ( ; l <= con.current ; l++)
217 	{
218 		line = con.text + (l%con.totallines)*con.linewidth;
219 		for(i=0; i<con.linewidth; i++)
220 			buffer[i] = line[i] & 0xff;
221 		for (x=con.linewidth-1 ; x>=0 ; x--)
222 		{
223 			if (buffer[x] == ' ')
224 				buffer[x] = 0;
225 			else
226 				break;
227 		}
228 #ifdef _WIN32
229 		Q_strcat(buffer, bufferlen, "\r\n");
230 #else
231 		Q_strcat(buffer, bufferlen, "\n");
232 #endif
233 		FS_Write(buffer, strlen(buffer), f);
234 	}
235 
236 	Hunk_FreeTempMemory( buffer );
237 	FS_FCloseFile( f );
238 }
239 
240 
241 /*
242 ================
243 Con_ClearNotify
244 ================
245 */
Con_ClearNotify(void)246 void Con_ClearNotify( void ) {
247 	int		i;
248 
249 	for ( i = 0 ; i < NUM_CON_TIMES ; i++ ) {
250 		con.times[i] = 0;
251 	}
252 }
253 
254 
255 
256 /*
257 ================
258 Con_CheckResize
259 
260 If the line width has changed, reformat the buffer.
261 ================
262 */
Con_CheckResize(void)263 void Con_CheckResize (void)
264 {
265 	int		i, j, width, oldwidth, oldtotallines, numlines, numchars;
266 	short	tbuf[CON_TEXTSIZE];
267 
268 	width = (SCREEN_WIDTH / SMALLCHAR_WIDTH) - 2;
269 
270 	if (width == con.linewidth)
271 		return;
272 
273 	if (width < 1)			// video hasn't been initialized yet
274 	{
275 		width = DEFAULT_CONSOLE_WIDTH;
276 		con.linewidth = width;
277 		con.totallines = CON_TEXTSIZE / con.linewidth;
278 		for(i=0; i<CON_TEXTSIZE; i++)
279 
280 			con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' ';
281 	}
282 	else
283 	{
284 		oldwidth = con.linewidth;
285 		con.linewidth = width;
286 		oldtotallines = con.totallines;
287 		con.totallines = CON_TEXTSIZE / con.linewidth;
288 		numlines = oldtotallines;
289 
290 		if (con.totallines < numlines)
291 			numlines = con.totallines;
292 
293 		numchars = oldwidth;
294 
295 		if (con.linewidth < numchars)
296 			numchars = con.linewidth;
297 
298 		Com_Memcpy (tbuf, con.text, CON_TEXTSIZE * sizeof(short));
299 		for(i=0; i<CON_TEXTSIZE; i++)
300 
301 			con.text[i] = (ColorIndex(COLOR_WHITE)<<8) | ' ';
302 
303 
304 		for (i=0 ; i<numlines ; i++)
305 		{
306 			for (j=0 ; j<numchars ; j++)
307 			{
308 				con.text[(con.totallines - 1 - i) * con.linewidth + j] =
309 						tbuf[((con.current - i + oldtotallines) %
310 							  oldtotallines) * oldwidth + j];
311 			}
312 		}
313 
314 		Con_ClearNotify ();
315 	}
316 
317 	con.current = con.totallines - 1;
318 	con.display = con.current;
319 }
320 
321 /*
322 ==================
323 Cmd_CompleteTxtName
324 ==================
325 */
Cmd_CompleteTxtName(char * args,int argNum)326 void Cmd_CompleteTxtName( char *args, int argNum ) {
327 	if( argNum == 2 ) {
328 		Field_CompleteFilename( "", "txt", qfalse, qtrue );
329 	}
330 }
331 
332 
333 /*
334 ================
335 Con_Init
336 ================
337 */
Con_Init(void)338 void Con_Init (void) {
339 	int		i;
340 
341 	con_notifytime = Cvar_Get ("con_notifytime", "3", 0);
342 	con_conspeed = Cvar_Get ("scr_conspeed", "3", 0);
343 
344 	Field_Clear( &g_consoleField );
345 	g_consoleField.widthInChars = g_console_field_width;
346 	for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) {
347 		Field_Clear( &historyEditLines[i] );
348 		historyEditLines[i].widthInChars = g_console_field_width;
349 	}
350 	CL_LoadConsoleHistory( );
351 
352 	Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
353 	Cmd_AddCommand ("messagemode", Con_MessageMode_f);
354 	Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
355 	Cmd_AddCommand ("messagemode3", Con_MessageMode3_f);
356 	Cmd_AddCommand ("messagemode4", Con_MessageMode4_f);
357 	Cmd_AddCommand ("clear", Con_Clear_f);
358 	Cmd_AddCommand ("condump", Con_Dump_f);
359 	Cmd_SetCommandCompletionFunc( "condump", Cmd_CompleteTxtName );
360 }
361 
362 
363 /*
364 ===============
365 Con_Linefeed
366 ===============
367 */
Con_Linefeed(qboolean skipnotify)368 void Con_Linefeed (qboolean skipnotify)
369 {
370 	int		i;
371 
372 	// mark time for transparent overlay
373 	if (con.current >= 0)
374 	{
375     if (skipnotify)
376 		  con.times[con.current % NUM_CON_TIMES] = 0;
377     else
378 		  con.times[con.current % NUM_CON_TIMES] = cls.realtime;
379 	}
380 
381 	con.x = 0;
382 	if (con.display == con.current)
383 		con.display++;
384 	con.current++;
385 	for(i=0; i<con.linewidth; i++)
386 		con.text[(con.current%con.totallines)*con.linewidth+i] = (ColorIndex(COLOR_WHITE)<<8) | ' ';
387 }
388 
389 /*
390 ================
391 CL_ConsolePrint
392 
393 Handles cursor positioning, line wrapping, etc
394 All console printing must go through this in order to be logged to disk
395 If no console is visible, the text will appear at the top of the game window
396 ================
397 */
CL_ConsolePrint(char * txt)398 void CL_ConsolePrint( char *txt ) {
399 	int		y;
400 	int		c, l;
401 	int		color;
402 	qboolean skipnotify = qfalse;		// NERVE - SMF
403 	int prev;							// NERVE - SMF
404 
405 	// TTimo - prefix for text that shows up in console but not in notify
406 	// backported from RTCW
407 	if ( !Q_strncmp( txt, "[skipnotify]", 12 ) ) {
408 		skipnotify = qtrue;
409 		txt += 12;
410 	}
411 
412 	// for some demos we don't want to ever show anything on the console
413 	if ( cl_noprint && cl_noprint->integer ) {
414 		return;
415 	}
416 
417 	if (!con.initialized) {
418 		con.color[0] =
419 		con.color[1] =
420 		con.color[2] =
421 		con.color[3] = 1.0f;
422 		con.linewidth = -1;
423 		Con_CheckResize ();
424 		con.initialized = qtrue;
425 	}
426 
427 	color = ColorIndex(COLOR_WHITE);
428 
429 	while ( (c = *txt) != 0 ) {
430 		if ( Q_IsColorString( txt ) ) {
431 			color = ColorIndex( *(txt+1) );
432 			txt += 2;
433 			continue;
434 		}
435 
436 		// count word length
437 		for (l=0 ; l< con.linewidth ; l++) {
438 			if ( txt[l] <= ' ') {
439 				break;
440 			}
441 
442 		}
443 
444 		// word wrap
445 		if (l != con.linewidth && (con.x + l >= con.linewidth) ) {
446 			Con_Linefeed(skipnotify);
447 
448 		}
449 
450 		txt++;
451 
452 		switch (c)
453 		{
454 		case '\n':
455 			Con_Linefeed (skipnotify);
456 			break;
457 		case '\r':
458 			con.x = 0;
459 			break;
460 		default:	// display character and advance
461 			y = con.current % con.totallines;
462 			con.text[y*con.linewidth+con.x] = (color << 8) | c;
463 			con.x++;
464 			if(con.x >= con.linewidth)
465 				Con_Linefeed(skipnotify);
466 			break;
467 		}
468 	}
469 
470 
471 	// mark time for transparent overlay
472 	if (con.current >= 0) {
473 		// NERVE - SMF
474 		if ( skipnotify ) {
475 			prev = con.current % NUM_CON_TIMES - 1;
476 			if ( prev < 0 )
477 				prev = NUM_CON_TIMES - 1;
478 			con.times[prev] = 0;
479 		}
480 		else
481 		// -NERVE - SMF
482 			con.times[con.current % NUM_CON_TIMES] = cls.realtime;
483 	}
484 }
485 
486 
487 /*
488 ==============================================================================
489 
490 DRAWING
491 
492 ==============================================================================
493 */
494 
495 
496 /*
497 ================
498 Con_DrawInput
499 
500 Draw the editline after a ] prompt
501 ================
502 */
Con_DrawInput(void)503 void Con_DrawInput (void) {
504 	int		y;
505 
506 	if ( cls.state != CA_DISCONNECTED && !(Key_GetCatcher( ) & KEYCATCH_CONSOLE ) ) {
507 		return;
508 	}
509 
510 	y = con.vislines - ( SMALLCHAR_HEIGHT * 2 );
511 
512 	re.SetColor( con.color );
513 
514 	SCR_DrawSmallChar( con.xadjust + 1 * SMALLCHAR_WIDTH, y, ']' );
515 
516 	Field_Draw( &g_consoleField, con.xadjust + 2 * SMALLCHAR_WIDTH, y,
517 		SCREEN_WIDTH - 3 * SMALLCHAR_WIDTH, qtrue, qtrue );
518 }
519 
520 
521 /*
522 ================
523 Con_DrawNotify
524 
525 Draws the last few lines of output transparently over the game top
526 ================
527 */
Con_DrawNotify(void)528 void Con_DrawNotify (void)
529 {
530 	int		x, v;
531 	short	*text;
532 	int		i;
533 	int		time;
534 	int		skip;
535 	int		currentColor;
536 
537 	currentColor = 7;
538 	re.SetColor( g_color_table[currentColor] );
539 
540 	v = 0;
541 	for (i= con.current-NUM_CON_TIMES+1 ; i<=con.current ; i++)
542 	{
543 		if (i < 0)
544 			continue;
545 		time = con.times[i % NUM_CON_TIMES];
546 		if (time == 0)
547 			continue;
548 		time = cls.realtime - time;
549 		if (time > con_notifytime->value*1000)
550 			continue;
551 		text = con.text + (i % con.totallines)*con.linewidth;
552 
553 		if (cl.snap.ps.pm_type != PM_INTERMISSION && Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME) ) {
554 			continue;
555 		}
556 
557 		for (x = 0 ; x < con.linewidth ; x++) {
558 			if ( ( text[x] & 0xff ) == ' ' ) {
559 				continue;
560 			}
561 			if ( ( (text[x]>>8)% NUMBER_OF_COLORS ) != currentColor ) {
562 				currentColor = (text[x]>>8) % NUMBER_OF_COLORS;
563 				re.SetColor( g_color_table[currentColor] );
564 			}
565 			SCR_DrawSmallChar( cl_conXOffset->integer + con.xadjust + (x+1)*SMALLCHAR_WIDTH, v, text[x] & 0xff );
566 		}
567 
568 		v += SMALLCHAR_HEIGHT;
569 	}
570 
571 	re.SetColor( NULL );
572 
573 	if (Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME) ) {
574 		return;
575 	}
576 
577 	// draw the chat line
578 	if ( Key_GetCatcher( ) & KEYCATCH_MESSAGE )
579 	{
580 		if (chat_team)
581 		{
582 			SCR_DrawBigString (8, v, "say_team:", 1.0f, qfalse );
583 			skip = 10;
584 		}
585 		else
586 		{
587 			SCR_DrawBigString (8, v, "say:", 1.0f, qfalse );
588 			skip = 5;
589 		}
590 
591 		Field_BigDraw( &chatField, skip * BIGCHAR_WIDTH, v,
592 			SCREEN_WIDTH - ( skip + 1 ) * BIGCHAR_WIDTH, qtrue, qtrue );
593 
594 		v += BIGCHAR_HEIGHT;
595 	}
596 
597 }
598 
599 /*
600 ================
601 Con_DrawSolidConsole
602 
603 Draws the console with the solid background
604 ================
605 */
Con_DrawSolidConsole(float frac)606 void Con_DrawSolidConsole( float frac ) {
607 	int				i, x, y;
608 	int				rows;
609 	short			*text;
610 	int				row;
611 	int				lines;
612 //	qhandle_t		conShader;
613 	int				currentColor;
614 	vec4_t			color;
615 
616 	lines = cls.glconfig.vidHeight * frac;
617 	if (lines <= 0)
618 		return;
619 
620 	if (lines > cls.glconfig.vidHeight )
621 		lines = cls.glconfig.vidHeight;
622 
623 	// on wide screens, we will center the text
624 	con.xadjust = 0;
625 	SCR_AdjustFrom640( &con.xadjust, NULL, NULL, NULL );
626 
627 	// draw the background
628 	y = frac * SCREEN_HEIGHT;
629 	if ( y < 1 ) {
630 		y = 0;
631 	}
632 	else {
633 		if ( cl_consoleType->integer ) {
634 			color[0] = cl_consoleType->integer > 1 ? cl_consoleColor[0]->value : 1.0f ;
635 			color[1] = cl_consoleType->integer > 1 ? cl_consoleColor[1]->value : 1.0f ;
636 			color[2] = cl_consoleType->integer > 1 ? cl_consoleColor[2]->value : 1.0f ;
637 			color[3] = cl_consoleColor[3]->value;
638 			re.SetColor( color );
639 		}
640 		if ( cl_consoleType->integer == 2 ) {
641 			SCR_DrawPic( 0, 0, SCREEN_WIDTH, y, cls.whiteShader );
642 		} else {
643 			SCR_DrawPic( 0, 0, SCREEN_WIDTH, y, cls.consoleShader );
644 		}
645 	}
646 
647 	color[0] = 1;
648 	color[1] = 0;
649 	color[2] = 0;
650 //	color[3] = 1;
651 	if( !cl_consoleType->integer )
652 		color[3] = 1;
653 	SCR_FillRect( 0, y, SCREEN_WIDTH, 2, color );
654 
655 
656 	// draw the version number
657 
658 	re.SetColor( g_color_table[ColorIndex(COLOR_RED)] );
659 
660 	i = strlen( Q3_VERSION );
661 
662 	for (x=0 ; x<i ; x++) {
663 		SCR_DrawSmallChar( cls.glconfig.vidWidth - ( i - x + 1 ) * SMALLCHAR_WIDTH,
664 			lines - SMALLCHAR_HEIGHT, Q3_VERSION[x] );
665 	}
666 
667 
668 	// draw the text
669 	con.vislines = lines;
670 	rows = (lines-SMALLCHAR_WIDTH)/SMALLCHAR_WIDTH;		// rows of text to draw
671 
672 	y = lines - (SMALLCHAR_HEIGHT*3);
673 
674 	// draw from the bottom up
675 	if (con.display != con.current)
676 	{
677 	// draw arrows to show the buffer is backscrolled
678 		re.SetColor( g_color_table[ColorIndex(COLOR_RED)] );
679 		for (x=0 ; x<con.linewidth ; x+=4)
680 			SCR_DrawSmallChar( con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, '^' );
681 		y -= SMALLCHAR_HEIGHT;
682 		rows--;
683 	}
684 
685 	row = con.display;
686 
687 	if ( con.x == 0 ) {
688 		row--;
689 	}
690 
691 	currentColor = 7;
692 	re.SetColor( g_color_table[currentColor] );
693 
694 	for (i=0 ; i<rows ; i++, y -= SMALLCHAR_HEIGHT, row--)
695 	{
696 		if (row < 0)
697 			break;
698 		if (con.current - row >= con.totallines) {
699 			// past scrollback wrap point
700 			continue;
701 		}
702 
703 		text = con.text + (row % con.totallines)*con.linewidth;
704 
705 		for (x=0 ; x<con.linewidth ; x++) {
706 			if ( ( text[x] & 0xff ) == ' ' ) {
707 				continue;
708 			}
709 
710 			if ( ( (text[x]>>8) % NUMBER_OF_COLORS ) != currentColor ) {
711 				currentColor = (text[x]>>8) % NUMBER_OF_COLORS;
712 				re.SetColor( g_color_table[currentColor] );
713 			}
714 			SCR_DrawSmallChar(  con.xadjust + (x+1)*SMALLCHAR_WIDTH, y, text[x] & 0xff );
715 		}
716 	}
717 
718 	// draw the input prompt, user text, and cursor if desired
719 	Con_DrawInput ();
720 
721 	re.SetColor( NULL );
722 }
723 
724 
725 
726 /*
727 ==================
728 Con_DrawConsole
729 ==================
730 */
Con_DrawConsole(void)731 void Con_DrawConsole( void ) {
732 	// check for console width changes from a vid mode change
733 	Con_CheckResize ();
734 
735 	// if disconnected, render console full screen
736 	if ( cls.state == CA_DISCONNECTED ) {
737 		if ( !( Key_GetCatcher( ) & (KEYCATCH_UI | KEYCATCH_CGAME)) ) {
738 			Con_DrawSolidConsole( 1.0 );
739 			return;
740 		}
741 	}
742 
743 	if ( con.displayFrac ) {
744 		Con_DrawSolidConsole( con.displayFrac );
745 	} else {
746 		// draw notify lines
747 		if ( cls.state == CA_ACTIVE ) {
748 			Con_DrawNotify ();
749 		}
750 	}
751 }
752 
753 //================================================================
754 
755 /*
756 ==================
757 Con_RunConsole
758 
759 Scroll it up or down
760 ==================
761 */
Con_RunConsole(void)762 void Con_RunConsole (void) {
763 	// decide on the destination height of the console
764 	if ( Key_GetCatcher( ) & KEYCATCH_CONSOLE )
765 		con.finalFrac = con.userFrac;
766 	else
767 		con.finalFrac = 0;				// none visible
768 
769 	// scroll towards the destination height
770 	if (con.finalFrac < con.displayFrac)
771 	{
772 		con.displayFrac -= con_conspeed->value*cls.realFrametime*0.001;
773 		if (con.finalFrac > con.displayFrac)
774 			con.displayFrac = con.finalFrac;
775 
776 	}
777 	else if (con.finalFrac > con.displayFrac)
778 	{
779 		con.displayFrac += con_conspeed->value*cls.realFrametime*0.001;
780 		if (con.finalFrac < con.displayFrac)
781 			con.displayFrac = con.finalFrac;
782 	}
783 
784 }
785 
786 
787 /*
788 ==================
789 Con_SetFrac
790 ==================
791 */
Con_SetFrac(const float conFrac)792 void Con_SetFrac(const float conFrac)
793 {
794 	// clamp the cvar value
795 	if (conFrac < .1f) {	// don't let the console be hidden
796 		con.userFrac = .1f;
797 	} else if (conFrac > 1.0f) {
798 		con.userFrac = 1.0f;
799 	} else {
800 		con.userFrac = conFrac;
801 	}
802 }
803 
Con_PageUp(void)804 void Con_PageUp( void ) {
805 	con.display -= 2;
806 	if ( con.current - con.display >= con.totallines ) {
807 		con.display = con.current - con.totallines + 1;
808 	}
809 }
810 
Con_PageDown(void)811 void Con_PageDown( void ) {
812 	con.display += 2;
813 	if (con.display > con.current) {
814 		con.display = con.current;
815 	}
816 }
817 
Con_Top(void)818 void Con_Top( void ) {
819 	con.display = con.totallines;
820 	if ( con.current - con.display >= con.totallines ) {
821 		con.display = con.current - con.totallines + 1;
822 	}
823 }
824 
Con_Bottom(void)825 void Con_Bottom( void ) {
826 	con.display = con.current;
827 }
828 
829 
Con_Close(void)830 void Con_Close( void ) {
831 	if ( !com_cl_running->integer ) {
832 		return;
833 	}
834 	Field_Clear( &g_consoleField );
835 	Con_ClearNotify ();
836 	Key_SetCatcher( Key_GetCatcher( ) & ~KEYCATCH_CONSOLE );
837 	con.finalFrac = 0;				// none visible
838 	con.displayFrac = 0;
839 }
840