1/*
2Copyright (C) 1996-1997 Id Software, Inc.
3
4This program is free software; you can redistribute it and/or
5modify it under the terms of the GNU General Public License
6as published by the Free Software Foundation; either version 2
7of the License, or (at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13See the GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19*/
20// console.c
21
22#include "quakedef.h"
23
24int			con_ormask;
25console_t	con_main;
26console_t	con_chat;
27console_t	*con;			// point to either con_main or con_chat
28
29int 		con_linewidth;	// characters across screen
30int			con_totallines;		// total lines in console scrollback
31
32float		con_cursorspeed = 4;
33
34
35cvar_t		con_notifytime = CVAR2("con_notifytime","3");		//seconds
36
37#define	NUM_CON_TIMES 4
38float		con_times[NUM_CON_TIMES];	// realtime time the line was generated
39								// for transparent notify lines
40
41int			con_vislines;
42int			con_notifylines;		// scan lines to clear for notify lines
43
44qboolean	con_debuglog;
45
46#define		MAXCMDLINE	256
47extern	char	key_lines[32][MAXCMDLINE];
48extern	int		edit_line;
49extern	int		key_linepos;
50
51
52qboolean	con_initialized;
53
54
55void Key_ClearTyping (void)
56{
57	key_lines[edit_line][1] = 0;	// clear any typing
58	key_linepos = 1;
59}
60
61/*
62================
63Con_ToggleConsole_f
64================
65*/
66void Con_ToggleConsole_f (void)
67{
68	Key_ClearTyping ();
69
70	if (key_dest == key_console)
71	{
72		if (cls.state == ca_active)
73			key_dest = key_game;
74	}
75	else
76		key_dest = key_console;
77
78	Con_ClearNotify ();
79}
80
81/*
82================
83Con_ToggleChat_f
84================
85*/
86void Con_ToggleChat_f (void)
87{
88	Key_ClearTyping ();
89
90	if (key_dest == key_console)
91	{
92		if (cls.state == ca_active)
93			key_dest = key_game;
94	}
95	else
96		key_dest = key_console;
97
98	Con_ClearNotify ();
99}
100
101/*
102================
103Con_Clear_f
104================
105*/
106void Con_Clear_f (void)
107{
108	Q_memset (con_main.text, ' ', CON_TEXTSIZE);
109	Q_memset (con_chat.text, ' ', CON_TEXTSIZE);
110}
111
112
113/*
114================
115Con_ClearNotify
116================
117*/
118void Con_ClearNotify (void)
119{
120	int		i;
121
122	for (i=0 ; i<NUM_CON_TIMES ; i++)
123		con_times[i] = 0;
124}
125
126
127/*
128================
129Con_MessageMode_f
130================
131*/
132void Con_MessageMode_f (void)
133{
134	chat_team = false;
135	key_dest = key_message;
136}
137
138/*
139================
140Con_MessageMode2_f
141================
142*/
143void Con_MessageMode2_f (void)
144{
145	chat_team = true;
146	key_dest = key_message;
147}
148
149/*
150================
151Con_Resize
152
153================
154*/
155void Con_Resize (console_t *con)
156{
157	int		i, j, width, oldwidth, oldtotallines, numlines, numchars;
158	char	tbuf[CON_TEXTSIZE];
159
160	width = (vid.width >> 3) - 2;
161
162	if (width == con_linewidth)
163		return;
164
165	if (width < 1)			// video hasn't been initialized yet
166	{
167		width = 38;
168		con_linewidth = width;
169		con_totallines = CON_TEXTSIZE / con_linewidth;
170		Q_memset (con->text, ' ', CON_TEXTSIZE);
171	}
172	else
173	{
174		oldwidth = con_linewidth;
175		con_linewidth = width;
176		oldtotallines = con_totallines;
177		con_totallines = CON_TEXTSIZE / con_linewidth;
178		numlines = oldtotallines;
179
180		if (con_totallines < numlines)
181			numlines = con_totallines;
182
183		numchars = oldwidth;
184
185		if (con_linewidth < numchars)
186			numchars = con_linewidth;
187
188		Q_memcpy (tbuf, con->text, CON_TEXTSIZE);
189		Q_memset (con->text, ' ', CON_TEXTSIZE);
190
191		for (i=0 ; i<numlines ; i++)
192		{
193			for (j=0 ; j<numchars ; j++)
194			{
195				con->text[(con_totallines - 1 - i) * con_linewidth + j] =
196						tbuf[((con->current - i + oldtotallines) %
197							  oldtotallines) * oldwidth + j];
198			}
199		}
200
201		Con_ClearNotify ();
202	}
203
204	con->current = con_totallines - 1;
205	con->display = con->current;
206}
207
208
209/*
210================
211Con_CheckResize
212
213If the line width has changed, reformat the buffer.
214================
215*/
216void Con_CheckResize (void)
217{
218	Con_Resize (&con_main);
219	Con_Resize (&con_chat);
220}
221
222
223/*
224================
225Con_Init
226================
227*/
228void Con_Init (void)
229{
230	con_debuglog = COM_CheckParm("-condebug");
231
232	con = &con_main;
233	con_linewidth = -1;
234	Con_CheckResize ();
235
236	Con_Printf ("Console initialized.\n");
237
238//
239// register our commands
240//
241	Cvar_RegisterVariable (&con_notifytime);
242
243	Cmd_AddCommand ("toggleconsole", Con_ToggleConsole_f);
244	Cmd_AddCommand ("togglechat", Con_ToggleChat_f);
245	Cmd_AddCommand ("messagemode", Con_MessageMode_f);
246	Cmd_AddCommand ("messagemode2", Con_MessageMode2_f);
247	Cmd_AddCommand ("clear", Con_Clear_f);
248	con_initialized = true;
249}
250
251
252/*
253===============
254Con_Linefeed
255===============
256*/
257void Con_Linefeed (void)
258{
259	con->x = 0;
260	if (con->display == con->current)
261		con->display++;
262	con->current++;
263	Q_memset (&con->text[(con->current%con_totallines)*con_linewidth]
264	, ' ', con_linewidth);
265}
266
267/*
268================
269Con_Print
270
271Handles cursor positioning, line wrapping, etc
272All console printing must go through this in order to be logged to disk
273If no console is visible, the notify window will pop up.
274================
275*/
276void Con_Print (char *txt)
277{
278	int		y;
279	int		c, l;
280	static int	cr;
281	int		mask;
282
283	if (txt[0] == 1 || txt[0] == 2)
284	{
285		mask = 128;		// go to colored text
286		txt++;
287	}
288	else
289		mask = 0;
290
291
292	while ( (c = *txt) )
293	{
294	// count word length
295		for (l=0 ; l< con_linewidth ; l++)
296			if ( txt[l] <= ' ')
297				break;
298
299	// word wrap
300		if (l != con_linewidth && (con->x + l > con_linewidth) )
301			con->x = 0;
302
303		txt++;
304
305		if (cr)
306		{
307			con->current--;
308			cr = false;
309		}
310
311
312		if (!con->x)
313		{
314			Con_Linefeed ();
315		// mark time for transparent overlay
316			if (con->current >= 0)
317				con_times[con->current % NUM_CON_TIMES] = realtime;
318		}
319
320		switch (c)
321		{
322		case '\n':
323			con->x = 0;
324			break;
325
326		case '\r':
327			con->x = 0;
328			cr = 1;
329			break;
330
331		default:	// display character and advance
332			y = con->current % con_totallines;
333			con->text[y*con_linewidth+con->x] = c | mask | con_ormask;
334			con->x++;
335			if (con->x >= con_linewidth)
336				con->x = 0;
337			break;
338		}
339
340	}
341}
342
343
344/*
345================
346Con_Printf
347
348Handles cursor positioning, line wrapping, etc
349================
350*/
351#define	MAXPRINTMSG	4096
352// FIXME: make a buffer size safe vsprintf?
353void Con_Printf (char *fmt, ...)
354{
355	va_list		argptr;
356	char		msg[MAXPRINTMSG];
357	static qboolean	inupdate;
358
359	va_start (argptr,fmt);
360	vsprintf (msg,fmt,argptr);
361	va_end (argptr);
362
363// also echo to debugging console
364	Sys_Printf ("%s", msg);	// also echo to debugging console
365
366// log all messages to file
367	if (con_debuglog)
368		Sys_DebugLog(va("%s/qconsole.log",com_gamedir), "%s", msg);
369
370	if (!con_initialized)
371		return;
372
373// write it to the scrollable buffer
374	Con_Print (msg);
375
376// update the screen immediately if the console is displayed
377	if (cls.state != ca_active)
378	{
379	// protect against infinite loop if something in SCR_UpdateScreen calls
380	// Con_Printd
381		if (!inupdate)
382		{
383			inupdate = true;
384			SCR_UpdateScreen ();
385			inupdate = false;
386		}
387	}
388}
389
390/*
391================
392Con_DPrintf
393
394A Con_Printf that only shows up if the "developer" cvar is set
395================
396*/
397void Con_DPrintf (char *fmt, ...)
398{
399	va_list		argptr;
400	char		msg[MAXPRINTMSG];
401
402	if (!developer.value)
403		return;			// don't confuse non-developers with techie stuff...
404
405	va_start (argptr,fmt);
406	vsprintf (msg,fmt,argptr);
407	va_end (argptr);
408
409	Con_Printf ("%s", msg);
410}
411
412/*
413==============================================================================
414
415DRAWING
416
417==============================================================================
418*/
419
420
421/*
422================
423Con_DrawInput
424
425The input line scrolls horizontally if typing goes beyond the right edge
426================
427*/
428void Con_DrawInput (void)
429{
430	int		y;
431	int		i;
432	char	*text;
433
434	if (key_dest != key_console && cls.state == ca_active)
435		return;		// don't draw anything (allways draw if not active)
436
437	text = key_lines[edit_line];
438
439// add the cursor frame
440	text[key_linepos] = 10+((int)(realtime*con_cursorspeed)&1);
441
442// fill out remainder with spaces
443	for (i=key_linepos+1 ; i< con_linewidth ; i++)
444		text[i] = ' ';
445
446//	prestep if horizontally scrolling
447	if (key_linepos >= con_linewidth)
448		text += 1 + key_linepos - con_linewidth;
449
450// draw it
451	y = con_vislines-22;
452
453	for (i=0 ; i<con_linewidth ; i++)
454		Draw_Character ( (i+1)<<3, con_vislines - 22, text[i]);
455
456// remove cursor
457	key_lines[edit_line][key_linepos] = 0;
458}
459
460
461/*
462================
463Con_DrawNotify
464
465Draws the last few lines of output transparently over the game top
466================
467*/
468void Con_DrawNotify (void)
469{
470	int		x, v;
471	char	*text;
472	int		i;
473	float	time;
474	char	*s;
475	int		skip;
476
477	v = 0;
478	for (i= con->current-NUM_CON_TIMES+1 ; i<=con->current ; i++)
479	{
480		if (i < 0)
481			continue;
482		time = con_times[i % NUM_CON_TIMES];
483		if (time == 0)
484			continue;
485		time = realtime - time;
486		if (time > con_notifytime.value)
487			continue;
488		text = con->text + (i % con_totallines)*con_linewidth;
489
490		clearnotify = 0;
491		scr_copytop = 1;
492
493		for (x = 0 ; x < con_linewidth ; x++)
494			Draw_Character ( (x+1)<<3, v, text[x]);
495
496		v += 8;
497	}
498
499
500	if (key_dest == key_message)
501	{
502		clearnotify = 0;
503		scr_copytop = 1;
504
505		if (chat_team)
506		{
507			Draw_String (8, v, "say_team:");
508			skip = 11;
509		}
510		else
511		{
512			Draw_String (8, v, "say:");
513			skip = 5;
514		}
515
516		s = chat_buffer;
517		if (chat_bufferlen > (int) ((vid.width>>3)-(skip+1)))
518			s += chat_bufferlen - ((vid.width>>3)-(skip+1));
519		x = 0;
520		while(s[x])
521		{
522			Draw_Character ( (x+skip)<<3, v, s[x]);
523			x++;
524		}
525		Draw_Character ( (x+skip)<<3, v, 10+((int)(realtime*con_cursorspeed)&1));
526		v += 8;
527	}
528
529	if (v > con_notifylines)
530		con_notifylines = v;
531}
532
533/*
534================
535Con_DrawConsole
536
537Draws the console with the solid background
538================
539*/
540void Con_DrawConsole (int lines)
541{
542	int				i, j, x, y, n;
543	int				rows;
544	char			*text;
545	int				row;
546	char			dlbar[1024];
547
548	if (lines <= 0)
549		return;
550
551// draw the background
552	Draw_ConsoleBackground (lines);
553
554// draw the text
555	con_vislines = lines;
556
557// changed to line things up better
558	rows = (lines-22)>>3;		// rows of text to draw
559
560	y = lines - 30;
561
562// draw from the bottom up
563	if (con->display != con->current)
564	{
565	// draw arrows to show the buffer is backscrolled
566		for (x=0 ; x<con_linewidth ; x+=4)
567			Draw_Character ( (x+1)<<3, y, '^');
568
569		y -= 8;
570		rows--;
571	}
572
573	row = con->display;
574	for (i=0 ; i<rows ; i++, y-=8, row--)
575	{
576		if (row < 0)
577			break;
578		if (con->current - row >= con_totallines)
579			break;		// past scrollback wrap point
580
581		text = con->text + (row % con_totallines)*con_linewidth;
582
583		for (x=0 ; x<con_linewidth ; x++)
584			Draw_Character ( (x+1)<<3, y, text[x]);
585	}
586
587	// draw the download bar
588	// figure out width
589	if (cls.download) {
590		if ((text = strrchr(cls.downloadname, '/')) != NULL)
591			text++;
592		else
593			text = cls.downloadname;
594
595		x = con_linewidth - ((con_linewidth * 7) / 40);
596		y = x - strlen(text) - 8;
597		i = con_linewidth/3;
598		if ((int) strlen(text) > i) {
599			y = x - i - 11;
600			strncpy(dlbar, text, i);
601			dlbar[i] = 0;
602			strcat(dlbar, "...");
603		} else
604			strcpy(dlbar, text);
605		strcat(dlbar, ": ");
606		i = strlen(dlbar);
607		dlbar[i++] = '\x80';
608		// where's the dot go?
609		if (cls.downloadpercent == 0)
610			n = 0;
611		else
612			n = y * cls.downloadpercent / 100;
613
614		for (j = 0; j < y; j++)
615			if (j == n)
616				dlbar[i++] = '\x83';
617			else
618				dlbar[i++] = '\x81';
619		dlbar[i++] = '\x82';
620		dlbar[i] = 0;
621
622		sprintf(dlbar + strlen(dlbar), " %02d%%", cls.downloadpercent);
623
624		// draw it
625		y = con_vislines-22 + 8;
626		for (i = 0; i < (int) strlen(dlbar); i++)
627			Draw_Character ( (i+1)<<3, y, dlbar[i]);
628	}
629
630
631// draw the input prompt, user text, and cursor if desired
632	Con_DrawInput ();
633}
634
635
636/*
637==================
638Con_NotifyBox
639==================
640*/
641void Con_NotifyBox (char *text)
642{
643	double		t1, t2;
644
645// during startup for sound / cd warnings
646	Con_Printf("\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");
647
648	Con_Printf (text);
649
650	Con_Printf ("Press a key.\n");
651	Con_Printf("\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");
652
653	key_count = -2;		// wait for a key down and up
654	key_dest = key_console;
655
656	do
657	{
658		t1 = Sys_DoubleTime ();
659		SCR_UpdateScreen ();
660		Sys_SendKeyEvents ();
661		t2 = Sys_DoubleTime ();
662		realtime += t2-t1;		// make the cursor blink
663	} while (key_count < 0);
664
665	Con_Printf ("\n");
666	key_dest = key_game;
667	realtime = 0;				// put the cursor back to invisible
668}
669
670
671/*
672==================
673Con_SafePrintf
674
675Okay to call even when the screen can't be updated
676==================
677*/
678void Con_SafePrintf (char *fmt, ...)
679{
680	va_list		argptr;
681	char		msg[1024];
682	int			temp;
683
684	va_start (argptr,fmt);
685	vsprintf (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