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, v
18 */
19 
20 //
21 // cl_console.c
22 //
23 
24 #include "cl_local.h"
25 
26 #define CON_TEXTSIZE	65536
27 #define CON_MAXTIMES	128
28 
29 #define CON_NOTIFYTIMES	( clamp (con_notifylines->intVal, 1, CON_MAXTIMES) )
30 
31 typedef struct console_s {
32 	qBool		initialized;
33 
34 	char		text[CON_TEXTSIZE];		// console text
35 	float		times[CON_MAXTIMES];	// cls.realTime time the line was generated
36 										// for transparent notify lines
37 
38 	int			orMask;
39 
40 	qBool		carriageReturn;			// last newline was '\r'
41 	int			xOffset;
42 
43 	int			lastColor;				// color before last newline
44 	int			lastStyle;				// style before last newline
45 	int			currentLine;			// line where next message will be printed
46 	int			display;				// bottom of console displays this line
47 
48 	float		visLines;
49 	int			totalLines;				// total lines in console scrollback
50 	int			lineWidth;				// characters across screen
51 
52 	float		currentDrop;			// aproaches con_DropHeight at scr_conspeed->floatVal
53 	float		dropHeight;				// 0.0 to 1.0 lines of console to display
54 } console_t;
55 
56 static console_t	cl_console;
57 static console_t	cl_chatConsole;
58 
59 /*
60 ================
61 CL_ClearNotifyLines
62 ================
63 */
CL_ClearNotifyLines(void)64 void CL_ClearNotifyLines (void)
65 {
66 	memset (&cl_console.times, 0, sizeof (cl_console.times));
67 	memset (&cl_chatConsole.times, 0, sizeof (cl_chatConsole.times));
68 }
69 
70 
71 /*
72 ================
73 CL_ConsoleClose
74 ================
75 */
CL_ConsoleClose(void)76 void CL_ConsoleClose (void)
77 {
78 	cl_console.currentDrop = 0;
79 	cl_chatConsole.currentDrop = 0;
80 
81 	Key_ClearTyping ();
82 	CL_ClearNotifyLines ();
83 	Key_ClearStates ();
84 }
85 
86 
87 /*
88 ================
89 CL_MoveConsoleDisplay
90 ================
91 */
CL_MoveConsoleDisplay(int value)92 void CL_MoveConsoleDisplay (int value)
93 {
94 	cl_console.display += value;
95 	if (cl_console.display > cl_console.currentLine)
96 		cl_console.display = cl_console.currentLine;
97 }
98 
99 
100 /*
101 ================
102 CL_SetConsoleDisplay
103 ================
104 */
CL_SetConsoleDisplay(qBool top)105 void CL_SetConsoleDisplay (qBool top)
106 {
107 	cl_console.display = cl_console.currentLine;
108 	if (top)
109 		cl_console.display -= cl_console.totalLines + 10;
110 }
111 
112 
113 /*
114 ================
115 CL_ConsoleCheckResize
116 
117 If the line width has changed, reformat the buffer.
118 ================
119 */
CL_ResizeConsole(console_t * console)120 static void CL_ResizeConsole (console_t *console)
121 {
122 	int		width, oldWidth;
123 	int		numLines, numChars;
124 	int		oldTotalLines;
125 	int		i, j;
126 	char	tempbuf[CON_TEXTSIZE];
127 	vec2_t	charSize;
128 
129 	if (cls.refConfig.vidWidth < 1) {
130 		// Video hasn't been initialized yet
131 		console->lineWidth = (640 / 8) - 2;
132 		console->totalLines = CON_TEXTSIZE / console->lineWidth;
133 		memset (console->text, ' ', CON_TEXTSIZE);
134 	}
135 	else {
136 		R_GetFontDimensions (NULL, 0, 0, 0, charSize);
137 
138 		width = (cls.refConfig.vidWidth / charSize[0]) - 2;
139 		if (width == console->lineWidth)
140 			return;
141 
142 		oldWidth = console->lineWidth;
143 		console->lineWidth = width;
144 
145 		oldTotalLines = console->totalLines;
146 		console->totalLines = CON_TEXTSIZE / console->lineWidth;
147 
148 		numLines = oldTotalLines;
149 		if (console->totalLines < numLines)
150 			numLines = console->totalLines;
151 
152 		numChars = oldWidth;
153 		if (console->lineWidth < numChars)
154 			numChars = console->lineWidth;
155 
156 		memcpy (tempbuf, console->text, CON_TEXTSIZE);
157 		memset (console->text, ' ', CON_TEXTSIZE);
158 
159 		for (i=0 ; i<numLines ; i++) {
160 			for (j=0 ; j<numChars ; j++) {
161 				console->text[(console->totalLines - 1 - i) * console->lineWidth + j] =
162 					tempbuf[((console->currentLine - i + oldTotalLines) % oldTotalLines) * oldWidth + j];
163 			}
164 		}
165 
166 		CL_ClearNotifyLines ();
167 	}
168 
169 	console->currentLine = max (1, console->totalLines - 1);
170 	console->display = console->currentLine;
171 }
172 
CL_ConsoleCheckResize(void)173 void CL_ConsoleCheckResize (void)
174 {
175 	CL_ResizeConsole (&cl_console);
176 	CL_ResizeConsole (&cl_chatConsole);
177 }
178 
179 
180 /*
181 ===============
182 CL_ConsoleLinefeed
183 ===============
184 */
CL_ConsoleLinefeed(console_t * console,qBool skipNotify)185 static void CL_ConsoleLinefeed (console_t *console, qBool skipNotify)
186 {
187 	// Line feed
188 	if (console->display == console->currentLine)
189 		console->display++;
190 
191 	console->currentLine++;
192 	memset (&console->text[(console->currentLine%console->totalLines)*console->lineWidth], ' ', console->lineWidth);
193 
194 	// Set the time for notify lines
195 	console->times[console->currentLine % CON_MAXTIMES] = (skipNotify) ? 0 : cls.realTime;
196 }
197 
198 
199 /*
200 ================
201 CL_ConsolePrintf
202 
203 Handles cursor positioning, line wrapping, etc
204 All console printing must go through this in order to be logged to disk
205 If no console is visible, the text will appear at the top of the game window
206 ================
207 */
CL_PrintToConsole(console_t * console,comPrint_t flags,const char * txt)208 static void CL_PrintToConsole (console_t *console, comPrint_t flags, const char *txt)
209 {
210 	int			y, l;
211 	int			orMask;
212 
213 	if (!console->initialized)
214 		return;
215 
216 	if (txt[0] == 1 || txt[0] == 2) {
217 		orMask = 128;
218 		txt++;
219 	}
220 	else
221 		orMask = 0;
222 
223 	while (*txt) {
224 		// Count word length
225 		for (l=0 ; l<console->lineWidth ; l++) {
226 			if (txt[l] <= ' ')
227 				break;
228 		}
229 
230 		// Word wrap
231 		if (l != console->lineWidth && console->xOffset + l > console->lineWidth)
232 			console->xOffset = 0;
233 
234 		if (console->carriageReturn) {
235 			console->currentLine = max (1, console->currentLine - 1);
236 			console->carriageReturn = qFalse;
237 		}
238 
239 		if (!console->xOffset) {
240 			// Feed a line
241 			CL_ConsoleLinefeed (console, (flags & PRNT_CONSOLE));
242 
243 			y = console->currentLine % console->totalLines;
244 			if (console->lastColor != -1 && y*console->lineWidth < CON_TEXTSIZE-2) {
245 				console->text[y*console->lineWidth] = COLOR_ESCAPE;
246 				console->text[y*console->lineWidth+1] = '0' + console->lastColor;
247 				console->xOffset += 2;
248 			}
249 
250 			if (console->lastStyle != -1 && y*console->lineWidth+console->xOffset < CON_TEXTSIZE-2) {
251 				console->text[y*console->lineWidth+console->xOffset] = COLOR_ESCAPE;
252 				console->text[y*console->lineWidth+console->xOffset+1] = console->lastStyle;
253 				console->xOffset += 2;
254 			}
255 		}
256 
257 		switch (*txt) {
258 		case '\n':
259 			console->lastColor = -1;
260 			console->lastStyle = -1;
261 			console->xOffset = 0;
262 			break;
263 
264 		case '\r':
265 			console->lastColor = -1;
266 			console->lastStyle = -1;
267 			console->xOffset = 0;
268 			console->carriageReturn = qTrue;
269 			break;
270 
271 		default:
272 			// Display character and advance
273 			y = console->currentLine % console->totalLines;
274 			console->text[y*console->lineWidth+console->xOffset] = *txt | orMask | console->orMask;
275 			if (++console->xOffset >= console->lineWidth)
276 				console->xOffset = 0;
277 
278 			// Get old color/style codes
279 			if (Q_IsColorString (txt)) {
280 				switch ((*(txt+1)) & 127) {
281 				case 's':
282 				case 'S':
283 					if (console->lastStyle == 'S')
284 						console->lastStyle = -1;
285 					else
286 						console->lastStyle = 'S';
287 					break;
288 
289 				case 'r':
290 				case 'R':
291 					console->lastStyle = -1;
292 					console->lastColor = -1;
293 					break;
294 
295 				case COLOR_BLACK:
296 				case COLOR_RED:
297 				case COLOR_GREEN:
298 				case COLOR_YELLOW:
299 				case COLOR_BLUE:
300 				case COLOR_CYAN:
301 				case COLOR_MAGENTA:
302 				case COLOR_WHITE:
303 				case COLOR_GREY:
304 					console->lastColor = Q_StrColorIndex (*(txt+1));
305 					break;
306 
307 				default:
308 					console->lastColor = -1;
309 					break;
310 				}
311 			}
312 			break;
313 		}
314 
315 		txt++;
316 	}
317 }
318 
CL_ConsolePrintf(comPrint_t flags,const char * txt)319 void CL_ConsolePrintf (comPrint_t flags, const char *txt)
320 {
321 	// Error/warning color coding
322 	if (flags & PRNT_ERROR)
323 		CL_PrintToConsole (&cl_console, flags, S_COLOR_RED);
324 	else if (flags & PRNT_WARNING)
325 		CL_PrintToConsole (&cl_console, flags, S_COLOR_YELLOW);
326 
327 	// High-bit character list
328 	if (flags & PRNT_CHATHUD) {
329 		cl_console.orMask = 128;
330 		cl_chatConsole.orMask = 128;
331 
332 		// Regular console
333 		if (con_chatHud->intVal == 2)
334 			flags |= PRNT_CONSOLE;
335 	}
336 
337 	// Regular console
338 	CL_PrintToConsole (&cl_console, flags, txt);
339 
340 	// Chat console
341 	if (flags & PRNT_CHATHUD) {
342 		CL_PrintToConsole (&cl_chatConsole, flags, txt);
343 
344 		cl_console.orMask = 0;
345 		cl_chatConsole.orMask = 0;
346 	}
347 }
348 
349 /*
350 ==============================================================================
351 
352 	CONSOLE COMMANDS
353 
354 ==============================================================================
355 */
356 
357 /*
358 ================
359 CL_ClearConsoleText_f
360 ================
361 */
CL_ClearConsoleText_f(void)362 static void CL_ClearConsoleText_f (void)
363 {
364 	// Reset line locations and clear the buffers
365 	cl_console.currentLine = max (1, cl_console.totalLines - 1);
366 	cl_console.display = cl_console.currentLine;
367 	memset (cl_console.text, ' ', CON_TEXTSIZE);
368 
369 	cl_chatConsole.currentLine = max (1, cl_chatConsole.totalLines - 1);
370 	cl_chatConsole.display = cl_chatConsole.currentLine;
371 	memset (cl_chatConsole.text, ' ', CON_TEXTSIZE);
372 }
373 
374 
375 /*
376 ================
377 CL_ConsoleDump_f
378 
379 Save the console contents out to a file
380 ================
381 */
CL_ConsoleDump_f(void)382 static void CL_ConsoleDump_f (void)
383 {
384 	int		l, x;
385 	char	*line;
386 	FILE	*f;
387 	char	buffer[1024];
388 	char	name[MAX_OSPATH];
389 
390 	if (Cmd_Argc () != 2) {
391 		Com_Printf (0, "usage: condump <filename>\n");
392 		return;
393 	}
394 
395 	if (strchr (Cmd_Argv (1), '.'))
396 		Q_snprintfz (name, sizeof (name), "%s/condumps/%s", FS_Gamedir(), Cmd_Argv(1));
397 	else
398 		Q_snprintfz (name, sizeof (name), "%s/condumps/%s.txt", FS_Gamedir(), Cmd_Argv (1));
399 
400 	Com_Printf (0, "Dumped console text to %s.\n", name);
401 	FS_CreatePath (name);
402 	f = fopen (name, "w");
403 	if (!f) {
404 		Com_Printf (PRNT_ERROR, "ERROR: CL_ConsoleDump_f: couldn't open, make sure the name you attempted is valid (%s).\n", name);
405 		return;
406 	}
407 
408 	// Skip empty lines
409 	for (l=cl_console.currentLine-cl_console.totalLines+1 ; l<=cl_console.currentLine ; l++) {
410 		line = cl_console.text + (l%cl_console.totalLines)*cl_console.lineWidth;
411 		for (x=0 ; x<cl_console.lineWidth ; x++)
412 			if (line[x] != ' ')
413 				break;
414 		if (x != cl_console.lineWidth)
415 			break;
416 	}
417 
418 	// Write the remaining lines
419 	buffer[cl_console.lineWidth] = 0;
420 	for ( ; l<=cl_console.currentLine ; l++) {
421 		line = cl_console.text + (l % cl_console.totalLines) * cl_console.lineWidth;
422 		strncpy (buffer, line, cl_console.lineWidth);
423 		for (x=cl_console.lineWidth-1 ; x>=0 ; x--) {
424 			if (buffer[x] == ' ')
425 				buffer[x] = '\0';
426 			else
427 				break;
428 		}
429 
430 		// Handle high-bit text
431 		for (x=0 ; buffer[x] ; x++)
432 			buffer[x] &= 127;
433 
434 		fprintf (f, "%s\n", buffer);
435 	}
436 
437 	fclose (f);
438 }
439 
440 
441 /*
442 ================
443 CL_ToggleConsole_f
444 ================
445 */
CL_ToggleConsole_f(void)446 void CL_ToggleConsole_f (void)
447 {
448 	static keyDest_t	oldKD;
449 
450 	// Kill the loading plaque
451 	SCR_EndLoadingPlaque ();
452 
453 	Key_ClearTyping ();
454 	CL_ClearNotifyLines ();
455 	Key_ClearStates ();
456 
457 	if (Key_GetDest () == KD_CONSOLE) {
458 		if (oldKD == KD_MESSAGE)
459 			Key_SetDest (KD_GAME);
460 		else
461 			Key_SetDest (oldKD);
462 	}
463 	else {
464 		oldKD = Key_GetDest ();
465 		Key_SetDest (KD_CONSOLE);
466 	}
467 }
468 
469 
470 /*
471 ================
472 CL_ToggleChat_f
473 ================
474 */
CL_ToggleChat_f(void)475 static void CL_ToggleChat_f (void)
476 {
477 	Key_ClearTyping ();
478 
479 	if (Key_GetDest () == KD_CONSOLE) {
480 		if (Com_ClientState () == CA_ACTIVE) {
481 			CL_CGModule_ForceMenuOff ();
482 			Key_SetDest (KD_GAME);
483 		}
484 	}
485 	else
486 		Key_SetDest (KD_CONSOLE);
487 
488 	CL_ClearNotifyLines ();
489 }
490 
491 
492 /*
493 ================
494 CL_MessageMode_f
495 ================
496 */
CL_MessageMode_f(void)497 static void CL_MessageMode_f (void)
498 {
499 	if (Com_ClientState () != CA_ACTIVE)
500 		return;
501 
502 	key_chatTeam = qFalse;
503 	Key_SetDest (KD_MESSAGE);
504 }
505 
506 
507 /*
508 ================
509 CL_MessageMode2_f
510 ================
511 */
CL_MessageMode2_f(void)512 static void CL_MessageMode2_f (void)
513 {
514 	if (Com_ClientState () != CA_ACTIVE)
515 		return;
516 
517 	key_chatTeam = qTrue;
518 	Key_SetDest (KD_MESSAGE);
519 }
520 
521 /*
522 ==============================================================================
523 
524 	INIT / SHUTDOWN
525 
526 ==============================================================================
527 */
528 
529 /*
530 ================
531 CL_ConsoleInit
532 ================
533 */
CL_ConsoleInit(void)534 void CL_ConsoleInit (void)
535 {
536 	if (dedicated->intVal)
537 		return;
538 
539 	Cmd_AddCommand ("toggleconsole",	CL_ToggleConsole_f,		"Toggles displaying the console");
540 	Cmd_AddCommand ("togglechat",		CL_ToggleChat_f,		"");
541 	Cmd_AddCommand ("messagemode",		CL_MessageMode_f,		"");
542 	Cmd_AddCommand ("messagemode2",		CL_MessageMode2_f,		"");
543 	Cmd_AddCommand ("clear",			CL_ClearConsoleText_f,	"Clears the console buffer");
544 	Cmd_AddCommand ("condump",			CL_ConsoleDump_f,		"Dumps the content of the console to file");
545 
546 	// Setup the console
547 	memset (&cl_console, 0, sizeof (console_t));
548 	cl_console.lineWidth = -1;
549 	cl_console.totalLines = -1;
550 	cl_console.lastColor = -1;
551 	cl_console.lastStyle = -1;
552 
553 	// Setup the chat console
554 	memset (&cl_chatConsole, 0, sizeof (console_t));
555 	cl_chatConsole.lineWidth = -1;
556 	cl_chatConsole.totalLines = -1;
557 	cl_chatConsole.lastColor = -1;
558 	cl_chatConsole.lastStyle = -1;
559 
560 	// Done
561 	CL_ConsoleCheckResize ();
562 
563 	cl_console.initialized = qTrue;
564 	cl_chatConsole.initialized = qTrue;
565 }
566 
567 /*
568 ==============================================================================
569 
570 	DRAWING
571 
572 ==============================================================================
573 */
574 
575 /*
576 ================
577 CL_DrawInput
578 
579 The input line scrolls horizontally if typing goes beyond the right edge
580 ================
581 */
CL_DrawInput(void)582 static void CL_DrawInput (void)
583 {
584 	char			*text;
585 	int				lastColor, lastStyle;
586 	int				colorCursorPos;
587 	int				byteOfs;
588 	int				byteLen;
589 	vec2_t			charSize;
590 
591 	if (Key_GetDest () == KD_MENU)
592 		return;
593 
594 	// Don't draw anything (always draw if not active)
595 	if (Key_GetDest () != KD_CONSOLE && Com_ClientState () == CA_ACTIVE)
596 		return;
597 
598 	R_GetFontDimensions (NULL, 0, 0, 0, charSize);
599 
600 	text = key_consoleBuffer[key_consoleEditLine];
601 
602 	// Convert byte offset to visible character count
603 	colorCursorPos = Q_ColorCharCount (text, key_consoleCursorPos);
604 
605 	// Prestep if horizontally scrolling
606 	if (colorCursorPos >= cl_console.lineWidth + 1) {
607 		byteOfs = Q_ColorCharOffset (text, colorCursorPos - cl_console.lineWidth);
608 		lastColor = Q_ColorStrLastColor (text, byteOfs);
609 		lastStyle = Q_ColorStrLastStyle (text, byteOfs);
610 		text += byteOfs;
611 		colorCursorPos = cl_console.lineWidth;
612 	}
613 	else {
614 		lastColor = Q_StrColorIndex (COLOR_WHITE);
615 		lastStyle = 0;
616 	}
617 
618 	byteLen = Q_ColorCharOffset (text, cl_console.lineWidth);
619 
620 	// Draw it
621 	R_DrawStringLen (NULL, 8, cl_console.visLines - (charSize[1] * 2), 0, 0, lastStyle, text, byteLen, Q_strColorTable[lastColor]);
622 
623 	// Add the cursor
624 	if ((Sys_UMilliseconds()>>8)&1)
625 		R_DrawChar (NULL, 8 + ((colorCursorPos - (key_insertOn ? 0.3 : 0)) * charSize[0]), cl_console.visLines - (charSize[1] * 2),
626 					0, 0, 0, key_insertOn ? '|' : 11, Q_colorWhite);
627 }
628 
629 
630 /*
631 ================
632 CL_DrawNotify
633 
634 Draws the last few lines of output transparently over the game top
635 ================
636 */
CL_DrawNotify(void)637 static void CL_DrawNotify (void)
638 {
639 	int		v, i, time;
640 	int		notifyLines;
641 	int		totalLines;
642 	char	*str;
643 	float	newScale;
644 	vec4_t	color;
645 	vec2_t	charSize;
646 
647 	Vec4Copy (Q_colorWhite, color);
648 
649 	// Make larger if desired
650 	R_GetFontDimensions (NULL, 0, 0, 0, charSize);
651 	if (con_notifylarge->intVal) {
652 		newScale = r_fontScale->floatVal * 1.25f;
653 
654 		charSize[0] *= 1.25f;
655 		charSize[1] *= 1.25f;
656 	}
657 	else {
658 		newScale = r_fontScale->floatVal;
659 	}
660 
661 	// Render notify lines
662 	if (con_notifylines->intVal) {
663 		for (totalLines=0, i=cl_console.currentLine ; i>cl_console.currentLine-CON_MAXTIMES+1 ; i--) {
664 			if (totalLines >= CON_NOTIFYTIMES)
665 				break;
666 
667 			time = cl_console.times[i % CON_MAXTIMES];
668 			if (time == 0)
669 				continue;
670 			time = cls.realTime - time;
671 			if (time > con_notifytime->floatVal * 1000)
672 				continue;
673 			totalLines++;
674 		}
675 
676 		for (v=charSize[1]*(totalLines-1), notifyLines=0, i=cl_console.currentLine ; i>cl_console.currentLine-CON_MAXTIMES+1 ; i--) {
677 			if (notifyLines >= CON_NOTIFYTIMES)
678 				break;
679 
680 			time = cl_console.times[i % CON_MAXTIMES];
681 			if (time == 0)
682 				continue;
683 			time = cls.realTime - time;
684 			if (time > con_notifytime->floatVal * 1000)
685 				continue;
686 			notifyLines++;
687 
688 			if (con_notifyfade->intVal)
689 				color[3] = 0.25 + 0.75 * (con_notifytime->floatVal - (time * 0.001)) / con_notifytime->floatVal;
690 
691 			R_DrawStringLen (NULL, 8, v, newScale, newScale, 0, cl_console.text + (i % cl_console.totalLines)*cl_console.lineWidth, cl_console.lineWidth, color);
692 
693 			v -= charSize[1];
694 		}
695 		v = charSize[1] * totalLines;
696 	}
697 	else {
698 		v = 0;
699 	}
700 
701 	//
702 	// Print messagemode input
703 	//
704 	if (Key_GetDest () == KD_MESSAGE) {
705 		int				skip;
706 		int				lastColor, lastStyle;
707 		int				colorCursorPos;
708 		int				byteOfs;
709 
710 		if (key_chatTeam)
711 			skip = R_DrawString (NULL, 4, v, newScale, newScale, 0, "say_team:", Q_colorWhite) + 1;
712 		else
713 			skip = R_DrawString (NULL, 4, v, newScale, newScale, 0, "say:", Q_colorWhite) + 1;
714 
715 		str = key_chatBuffer[key_chatEditLine];
716 
717 		// Convert byte offset to visible character count
718 		colorCursorPos = Q_ColorCharCount (str, key_chatCursorPos) + skip + 1;
719 
720 		// Prestep if horizontally scrolling
721 		if (colorCursorPos >= (int)(cls.refConfig.vidWidth/charSize[0])) {
722 			byteOfs = Q_ColorCharOffset (str, colorCursorPos - (int)(cls.refConfig.vidWidth/charSize[0]));
723 
724 			lastColor = Q_ColorStrLastColor (str, byteOfs);
725 			lastStyle = Q_ColorStrLastStyle (str, byteOfs);
726 
727 			str += byteOfs;
728 		}
729 		else {
730 			lastColor = Q_StrColorIndex (COLOR_WHITE);
731 			lastStyle = 0;
732 		}
733 
734 		R_DrawString (NULL, skip * charSize[0], v, newScale, newScale, lastStyle, str, Q_strColorTable[lastColor]);
735 
736 		// Add cursor
737 		if ((Sys_UMilliseconds()>>8)&1) {
738 			int charCount = Q_ColorCharCount (key_chatBuffer[key_chatEditLine], key_chatCursorPos) + skip;
739 			if (charCount > (int)(cls.refConfig.vidWidth/charSize[0]) - 1)
740 				charCount = (int)(cls.refConfig.vidWidth/charSize[0]) - 1;
741 
742 			R_DrawChar (NULL, ((charCount - (key_insertOn ? 0.3 : 0)) * charSize[0]), v, newScale, newScale, 0,
743 						key_insertOn ? '|' : 11, Q_colorWhite);
744 		}
745 
746 		v += charSize[1];
747 	}
748 }
749 
750 
751 /*
752 ==================
753 CL_DrawChatHud
754 ==================
755 */
CL_DrawChatHud(void)756 static void CL_DrawChatHud (void)
757 {
758 	int		totalLines, v, i;
759 	char	*text;
760 	vec2_t	charSize;
761 
762 	if (Com_ClientState () != CA_ACTIVE)
763 		return;
764 	if (Key_GetDest () == KD_MENU)
765 		return;
766 	if (cls.mapLoading)
767 		return;
768 
769 	R_GetFontDimensions (NULL, 0, 0, 0, charSize);
770 
771 	if (con_chatHudPosY->floatVal < 0)
772 		v = cls.refConfig.vidHeight - (charSize[1] * -con_chatHudPosY->floatVal);
773 	else
774 		v = cls.refConfig.vidHeight + (charSize[1] * con_chatHudPosY->floatVal);
775 
776 	totalLines = 0;
777 	for (i=cl_chatConsole.currentLine ; i>cl_chatConsole.currentLine-CON_MAXTIMES+1 ; i--) {
778 		if (totalLines == con_chatHudLines->intVal)
779 			break;
780 
781 		text = cl_chatConsole.text + (i % cl_chatConsole.totalLines)*cl_chatConsole.lineWidth;
782 		R_DrawStringLen (NULL, con_chatHudPosX->floatVal, v, 0, 0, con_chatHudShadow->intVal ? FS_SHADOW : 0, text, cl_chatConsole.lineWidth, Q_colorWhite);
783 
784 		totalLines++;
785 		v -= charSize[1];
786 	}
787 }
788 
789 
790 /*
791 ==================
792 CL_RunConsole
793 
794 Scroll it up or down
795 ==================
796 */
CL_RunConsole(void)797 static void CL_RunConsole (void)
798 {
799 	CL_ConsoleCheckResize ();
800 
801 	// Decide on the height of the console
802 	if (Key_GetDest () == KD_CONSOLE)
803 		cl_console.dropHeight = con_drop->floatVal;
804 	else
805 		cl_console.dropHeight = 0;			// none visible
806 
807 	if (cl_console.dropHeight < cl_console.currentDrop) {
808 		cl_console.currentDrop -= scr_conspeed->floatVal*cls.refreshFrameTime;
809 		if (cl_console.dropHeight > cl_console.currentDrop)
810 			cl_console.currentDrop = cl_console.dropHeight;
811 	}
812 	else if (cl_console.dropHeight > cl_console.currentDrop) {
813 		cl_console.currentDrop += scr_conspeed->floatVal*cls.refreshFrameTime;
814 		if (cl_console.dropHeight < cl_console.currentDrop)
815 			cl_console.currentDrop = cl_console.dropHeight;
816 	}
817 }
818 
819 
820 /*
821 ================
822 CL_DrawConsole
823 ================
824 */
CL_DrawConsole(void)825 void CL_DrawConsole (void)
826 {
827 	float		frac = 0.0f;
828 	int			i, row;
829 	float		x, y, lines;
830 	vec4_t		conColor;
831 	char		version[32];
832 	vec2_t		charSize;
833 
834 	R_GetFontDimensions (NULL, 0, 0, 0, charSize);
835 
836 	// Advance for next frame
837 	CL_RunConsole ();
838 
839 	// Draw the chat hud
840 	if (con_chatHud->intVal && con_chatHudLines->intVal > 0)
841 		CL_DrawChatHud ();
842 
843 	if (cl_console.currentDrop)
844 		frac = cl_console.currentDrop;
845 	else if (Com_ClientState () == CA_ACTIVE && (Key_GetDest () == KD_GAME || Key_GetDest () == KD_MESSAGE) && !cls.mapLoading && !cl.cin.time) {
846 		CL_DrawNotify ();	// Only draw notify in game
847 		return;
848 	}
849 
850 	// Check if it's even visible
851 	cl_console.visLines = cls.refConfig.vidHeight * clamp (frac, 0, 1);
852 	if (cl_console.visLines <= 0)
853 		return;
854 
855 	// Draw the background
856 	conColor[0] = Q_colorWhite[0];
857 	conColor[1] = Q_colorWhite[1];
858 	conColor[2] = Q_colorWhite[2];
859 	conColor[3] = con_alpha->floatVal;
860 	R_DrawPic (clMedia.consoleShader, 0, 0, -(cls.refConfig.vidHeight - cls.refConfig.vidHeight*frac), cls.refConfig.vidWidth, cls.refConfig.vidHeight, 0, 0, 1, 1, conColor);
861 
862 	// Version
863 	Q_snprintfz (version, sizeof (version), "EGL v%s", EGL_VERSTR);
864 	R_DrawString (NULL, cls.refConfig.vidWidth - (strlen(version) * charSize[0]) - 2,
865 				cl_console.visLines - charSize[1],
866 				0, 0, FS_SHADOW, version, Q_colorCyan);
867 
868 	// Time if desired
869 	if (con_clock->intVal) {
870 		char		clockStr[16];
871 		time_t		ctime;
872 		struct tm	*ltime;
873 
874 		ctime = time (NULL);
875 		ltime = localtime (&ctime);
876 		strftime (clockStr, sizeof (clockStr), "%I:%M %p", ltime);
877 
878 		// Kill the initial zero
879 		if (clockStr[0] == '0') {
880 			for (i=0 ; i<(int)(strlen (clockStr) + 1) ; i++)
881 				clockStr[i] = clockStr[i+1];
882 			clockStr[i+1] = '\0';
883 		}
884 
885 		R_DrawString (NULL, cls.refConfig.vidWidth - (strlen (clockStr) * charSize[0]) - 2,
886 					cl_console.visLines - (charSize[1] * 2),
887 					0, 0, FS_SHADOW, clockStr, Q_colorCyan);
888 	}
889 
890 	// Draw the console text
891 	y = cl_console.visLines - (charSize[1] * 3);	// start point (from the bottom)
892 	lines = (y / charSize[1]);						// lines of text to draw
893 
894 	// Draw arrows to show the buffer is backscrolled
895 	if (cl_console.display != cl_console.currentLine) {
896 		for (x=0 ; x<(cl_console.lineWidth+1)*charSize[0] ; x+=charSize[0]*2)
897 			R_DrawChar (NULL, x + (charSize[0] * 0.5), y, 0, 0, FS_SHADOW, '^', Q_colorRed);
898 
899 		y -= charSize[1];
900 		lines--;
901 	}
902 
903 	// Draw the print, from the bottom up
904 	for (i=0, row=cl_console.display ; i<lines ; i++, y-=charSize[1], row--) {
905 		if (row < 0)
906 			break;
907 		if (cl_console.currentLine - row >= cl_console.totalLines)
908 			break;	// past scrollback wrap point
909 		if (y < -charSize[1])
910 			break;
911 
912 		R_DrawStringLen (NULL, 8, y, 0, 0, 0, cl_console.text + (row % cl_console.totalLines) * cl_console.lineWidth, cl_console.lineWidth, Q_colorWhite);
913 	}
914 
915 	// Draw the input prompt, user text, and cursor if desired
916 	CL_DrawInput ();
917 }
918