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