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
21// screen.c -- master for refresh, status bar, console, chat, notify, etc
22
23#include "quakedef.h"
24
25/*
26
27background clear
28rendering
29turtle/net/ram icons
30sbar
31centerprint / slow centerprint
32notify lines
33intermission / finale overlay
34loading plaque
35console
36menu
37
38required background clears
39required update regions
40
41
42syncronous draw mode or async
43One off screen buffer, with updates either copied or xblited
44Need to double buffer?
45
46
47async draw will require the refresh area to be cleared, because it will be
48xblited, but sync draw can just ignore it.
49
50sync
51draw
52
53CenterPrint ()
54SlowPrint ()
55Screen_Update ();
56Con_Printf ();
57
58net
59turn off messages option
60
61the refresh is allways rendered, unless the console is full screen
62
63
64console is:
65	notify lines
66	half
67	full
68
69
70*/
71
72
73int			glx, gly, glwidth, glheight;
74
75// only the refresh window will be updated unless these variables are flagged
76int			scr_copytop;
77int			scr_copyeverything;
78
79float		scr_con_current;
80float		scr_conlines;		// lines of console to display
81
82float		oldscreensize, oldfov;
83cvar_t          scr_viewsize = CVAR3("viewsize","100", true);
84cvar_t          scr_fov = CVAR2("fov","90"); // 10 - 170
85cvar_t          scr_conspeed = CVAR2("scr_conspeed","300");
86cvar_t          scr_centertime = CVAR2("scr_centertime","2");
87cvar_t          scr_showram = CVAR2("showram","1");
88cvar_t          scr_showturtle = CVAR2("showturtle","0");
89cvar_t          scr_showpause = CVAR2("showpause","1");
90cvar_t          scr_printspeed = CVAR2("scr_printspeed","8");
91cvar_t			scr_allowsnap = CVAR2("scr_allowsnap", "1");
92cvar_t			gl_triplebuffer = CVAR3("gl_triplebuffer", "1", true );
93extern	cvar_t	crosshair;
94
95qboolean	scr_initialized;		// ready to draw
96
97qpic_t		*scr_ram;
98qpic_t		*scr_net;
99qpic_t		*scr_turtle;
100
101int			scr_fullupdate;
102
103int			clearconsole;
104int			clearnotify;
105
106// int			sb_lines;
107
108viddef_t	vid;				// global video state
109
110vrect_t		scr_vrect;
111
112qboolean	scr_disabled_for_loading;
113qboolean	scr_drawloading;
114float		scr_disabled_time;
115
116qboolean	block_drawing;
117
118void SCR_ScreenShot_f (void);
119
120/*
121===============================================================================
122
123CENTER PRINTING
124
125===============================================================================
126*/
127
128char		scr_centerstring[1024];
129float		scr_centertime_start;	// for slow victory printing
130float		scr_centertime_off;
131int			scr_center_lines;
132int			scr_erase_lines;
133int			scr_erase_center;
134
135/*
136==============
137SCR_CenterPrint
138
139Called for important messages that should stay in the center of the screen
140for a few moments
141==============
142*/
143void SCR_CenterPrint (char *str)
144{
145	strncpy (scr_centerstring, str, sizeof(scr_centerstring)-1);
146	scr_centertime_off = scr_centertime.value;
147	scr_centertime_start = cl.time;
148
149// count the number of lines for centering
150	scr_center_lines = 1;
151	while (*str)
152	{
153		if (*str == '\n')
154			scr_center_lines++;
155		str++;
156	}
157}
158
159
160void SCR_DrawCenterString (void)
161{
162	char	*start;
163	int		l;
164	int		j;
165	int		x, y;
166	int		remaining;
167
168// the finale prints the characters one at a time
169	if (cl.intermission)
170		remaining = (int) (scr_printspeed.value * (cl.time - scr_centertime_start));
171	else
172		remaining = 9999;
173
174	scr_erase_center = 0;
175	start = scr_centerstring;
176
177	if (scr_center_lines <= 4)
178		y = (int)(vid.height*0.35);
179	else
180		y = 48;
181
182	do
183	{
184	// scan the width of the line
185		for (l=0 ; l<40 ; l++)
186			if (start[l] == '\n' || !start[l])
187				break;
188		x = (vid.width - l*8)/2;
189		for (j=0 ; j<l ; j++, x+=8)
190		{
191			Draw_Character (x, y, start[j]);
192			if (!remaining--)
193				return;
194		}
195
196		y += 8;
197
198		while (*start && *start != '\n')
199			start++;
200
201		if (!*start)
202			break;
203		start++;		// skip the \n
204	} while (1);
205}
206
207void SCR_CheckDrawCenterString (void)
208{
209	scr_copytop = 1;
210	if (scr_center_lines > scr_erase_lines)
211		scr_erase_lines = scr_center_lines;
212
213	scr_centertime_off -= host_frametime;
214
215	if (scr_centertime_off <= 0 && !cl.intermission)
216		return;
217	if (key_dest != key_game)
218		return;
219
220	SCR_DrawCenterString ();
221}
222
223//=============================================================================
224
225/*
226====================
227CalcFov
228====================
229*/
230float CalcFov (float fov_x, float width, float height)
231{
232        float   a;
233        float   x;
234
235        if (fov_x < 1 || fov_x > 179)
236                Sys_Error ("Bad fov: %f", fov_x);
237
238        x = width/tan(fov_x/360*M_PI);
239
240        a = atan (height/x);
241
242        a = a*360/M_PI;
243
244        return a;
245}
246
247/*
248=================
249SCR_CalcRefdef
250
251Must be called whenever vid changes
252Internal use only
253=================
254*/
255static void SCR_CalcRefdef (void)
256{
257	vrect_t		vrect;
258	float		size;
259	int		h;
260	qboolean		full = false;
261
262
263	scr_fullupdate = 0;		// force a background redraw
264	vid.recalc_refdef = 0;
265
266// force the status bar to redraw
267	Sbar_Changed ();
268
269//========================================
270
271// bound viewsize
272	if (scr_viewsize.value < 30)
273		Cvar_Set ("viewsize","30");
274	if (scr_viewsize.value > 120)
275		Cvar_Set ("viewsize","120");
276
277// bound field of view
278	if (scr_fov.value < 10)
279		Cvar_Set ("fov","10");
280	if (scr_fov.value > 170)
281		Cvar_Set ("fov","170");
282
283// intermission is always full screen
284	if (cl.intermission)
285		size = 120;
286	else
287		size = scr_viewsize.value;
288
289	if (size >= 120)
290		sb_lines = 0;		// no status bar at all
291	else if (size >= 110)
292		sb_lines = 24;		// no inventory
293	else
294		sb_lines = 24+16+8;
295
296	if (scr_viewsize.value >= 100.0) {
297		full = true;
298		size = 100.0;
299	} else
300		size = scr_viewsize.value;
301	if (cl.intermission)
302	{
303		full = true;
304		size = 100;
305		sb_lines = 0;
306	}
307	size /= 100.0;
308
309	h = vid.height - sb_lines;
310
311	r_refdef.vrect.width = (int) (vid.width * size);
312	if (r_refdef.vrect.width < 96)
313	{
314		size = 96.0 / r_refdef.vrect.width;
315		r_refdef.vrect.width = 96;	// min for icons
316	}
317
318	r_refdef.vrect.height = (int)(vid.height * size);
319	if ((int)(r_refdef.vrect.height) > (int)(vid.height - sb_lines))
320		r_refdef.vrect.height = vid.height - sb_lines;
321	if ((int)(r_refdef.vrect.height) > (int)(vid.height))
322			r_refdef.vrect.height = vid.height;
323	r_refdef.vrect.x = (vid.width - r_refdef.vrect.width)/2;
324	if (full)
325		r_refdef.vrect.y = 0;
326	else
327		r_refdef.vrect.y = (h - r_refdef.vrect.height)/2;
328
329	r_refdef.fov_x = scr_fov.value;
330	r_refdef.fov_y = CalcFov (r_refdef.fov_x, r_refdef.vrect.width, r_refdef.vrect.height);
331
332	scr_vrect = r_refdef.vrect;
333}
334
335
336/*
337=================
338SCR_SizeUp_f
339
340Keybinding command
341=================
342*/
343void SCR_SizeUp_f (void)
344{
345	Cvar_SetValue ("viewsize",scr_viewsize.value+10);
346	vid.recalc_refdef = 1;
347}
348
349
350/*
351=================
352SCR_SizeDown_f
353
354Keybinding command
355=================
356*/
357void SCR_SizeDown_f (void)
358{
359	Cvar_SetValue ("viewsize",scr_viewsize.value-10);
360	vid.recalc_refdef = 1;
361}
362
363//============================================================================
364
365/*
366==================
367SCR_Init
368==================
369*/
370void SCR_Init (void)
371{
372
373	Cvar_RegisterVariable (&scr_fov);
374	Cvar_RegisterVariable (&scr_viewsize);
375	Cvar_RegisterVariable (&scr_conspeed);
376	Cvar_RegisterVariable (&scr_showram);
377	Cvar_RegisterVariable (&scr_showturtle);
378	Cvar_RegisterVariable (&scr_showpause);
379	Cvar_RegisterVariable (&scr_centertime);
380	Cvar_RegisterVariable (&scr_printspeed);
381	Cvar_RegisterVariable (&gl_triplebuffer);
382
383//
384// register our commands
385//
386	Cmd_AddCommand ("screenshot",SCR_ScreenShot_f);
387	Cmd_AddCommand ("sizeup",SCR_SizeUp_f);
388	Cmd_AddCommand ("sizedown",SCR_SizeDown_f);
389
390	scr_ram = Draw_PicFromWad ("ram");
391	scr_net = Draw_PicFromWad ("net");
392	scr_turtle = Draw_PicFromWad ("turtle");
393
394	scr_initialized = true;
395}
396
397
398
399/*
400==============
401SCR_DrawRam
402==============
403*/
404void SCR_DrawRam (void)
405{
406	if (!scr_showram.value)
407		return;
408
409	if (!r_cache_thrash)
410		return;
411
412	Draw_Pic (scr_vrect.x+32, scr_vrect.y, scr_ram);
413}
414
415/*
416==============
417SCR_DrawTurtle
418==============
419*/
420void SCR_DrawTurtle (void)
421{
422	static int	count;
423
424	if (!scr_showturtle.value)
425		return;
426
427	if (host_frametime < 0.1)
428	{
429		count = 0;
430		return;
431	}
432
433	count++;
434	if (count < 3)
435		return;
436
437	Draw_Pic (scr_vrect.x, scr_vrect.y, scr_turtle);
438}
439
440/*
441==============
442SCR_DrawNet
443==============
444*/
445void SCR_DrawNet (void)
446{
447	if (realtime - cl.last_received_message < 0.3)
448		return;
449	if (cls.demoplayback)
450		return;
451
452	Draw_Pic (scr_vrect.x+64, scr_vrect.y, scr_net);
453}
454
455/*
456==============
457DrawPause
458==============
459*/
460void SCR_DrawPause (void)
461{
462	qpic_t	*pic;
463
464	if (!scr_showpause.value)		// turn off for screenshots
465		return;
466
467	if (!cl.paused)
468		return;
469
470	pic = Draw_CachePic ("gfx/pause.lmp");
471	Draw_Pic ( (vid.width - pic->width)/2,
472		(vid.height - 48 - pic->height)/2, pic);
473}
474
475
476
477/*
478==============
479SCR_DrawLoading
480==============
481*/
482void SCR_DrawLoading (void)
483{
484	qpic_t	*pic;
485
486	if (!scr_drawloading)
487		return;
488
489	pic = Draw_CachePic ("gfx/loading.lmp");
490	Draw_Pic ( (vid.width - pic->width)/2,
491		(vid.height - 48 - pic->height)/2, pic);
492}
493
494
495
496//=============================================================================
497
498
499/*
500==================
501SCR_SetUpToDrawConsole
502==================
503*/
504void SCR_SetUpToDrawConsole (void)
505{
506	Con_CheckResize ();
507
508	if (scr_drawloading)
509		return;		// never a console with loading plaque
510
511// decide on the height of the console
512	con_forcedup = !cl.worldmodel || cls.signon != SIGNONS;
513
514	if (con_forcedup)
515	{
516		scr_conlines = vid.height;		// full screen
517		scr_con_current = scr_conlines;
518	}
519	else if (key_dest == key_console)
520		scr_conlines = vid.height/2;	// half screen
521	else
522		scr_conlines = 0;				// none visible
523
524	if (scr_conlines < scr_con_current)
525	{
526		scr_con_current -= scr_conspeed.value*host_frametime;
527		if (scr_conlines > scr_con_current)
528			scr_con_current = scr_conlines;
529
530	}
531	else if (scr_conlines > scr_con_current)
532	{
533		scr_con_current += scr_conspeed.value*host_frametime;
534		if (scr_conlines < scr_con_current)
535			scr_con_current = scr_conlines;
536	}
537
538	if (clearconsole++ < vid.numpages)
539	{
540		Sbar_Changed ();
541	}
542	else if (clearnotify++ < vid.numpages)
543	{
544	}
545	else
546		con_notifylines = 0;
547}
548
549/*
550==================
551SCR_DrawConsole
552==================
553*/
554void SCR_DrawConsole (void)
555{
556	if (scr_con_current)
557	{
558		scr_copyeverything = 1;
559		Con_DrawConsole ((int) scr_con_current, true);
560		clearconsole = 0;
561	}
562	else
563	{
564		if (key_dest == key_game || key_dest == key_message)
565			Con_DrawNotify ();	// only draw notify in game
566	}
567}
568
569
570/*
571==============================================================================
572
573						SCREEN SHOTS
574
575==============================================================================
576*/
577
578typedef struct _TargaHeader {
579	unsigned char 	id_length, colormap_type, image_type;
580	unsigned short	colormap_index, colormap_length;
581	unsigned char	colormap_size;
582	unsigned short	x_origin, y_origin, width, height;
583	unsigned char	pixel_size, attributes;
584} TargaHeader;
585
586
587/*
588==================
589SCR_ScreenShot_f
590==================
591*/
592void SCR_ScreenShot_f (void)
593{
594	byte		*buffer;
595	char		pcxname[80];
596	char		checkname[MAX_OSPATH];
597	int			i, c, temp;
598//
599// find a file name to save it to
600//
601	strcpy(pcxname,"quake00.tga");
602
603	for (i=0 ; i<=99 ; i++)
604	{
605		pcxname[5] = i/10 + '0';
606		pcxname[6] = i%10 + '0';
607		sprintf (checkname, "%s/%s", com_gamedir, pcxname);
608		if (Sys_FileTime(checkname) == -1)
609			break;	// file doesn't exist
610	}
611	if (i==100)
612	{
613		Con_Printf ("SCR_ScreenShot_f: Couldn't create a PCX file\n");
614		return;
615 	}
616
617
618	buffer = (byte*) malloc(glwidth*glheight*3 + 18);
619	memset (buffer, 0, 18);
620	buffer[2] = 2;		// uncompressed type
621	buffer[12] = glwidth&255;
622	buffer[13] = glwidth>>8;
623	buffer[14] = glheight&255;
624	buffer[15] = glheight>>8;
625	buffer[16] = 24;	// pixel size
626
627	glReadPixels (glx, gly, glwidth, glheight, GL_RGB, GL_UNSIGNED_BYTE, buffer+18 );
628
629	// swap rgb to bgr
630	c = 18+glwidth*glheight*3;
631	for (i=18 ; i<c ; i+=3)
632	{
633		temp = buffer[i];
634		buffer[i] = buffer[i+2];
635		buffer[i+2] = temp;
636	}
637	COM_WriteFile (pcxname, buffer, glwidth*glheight*3 + 18 );
638
639	free (buffer);
640	Con_Printf ("Wrote %s\n", pcxname);
641}
642
643
644//=============================================================================
645
646
647/*
648===============
649SCR_BeginLoadingPlaque
650
651================
652*/
653void SCR_BeginLoadingPlaque (void)
654{
655	S_StopAllSounds (true);
656
657	if (cls.state != ca_connected)
658		return;
659	if (cls.signon != SIGNONS)
660		return;
661
662// redraw with no console and the loading plaque
663	Con_ClearNotify ();
664	scr_centertime_off = 0;
665	scr_con_current = 0;
666
667	scr_drawloading = true;
668	scr_fullupdate = 0;
669	Sbar_Changed ();
670	SCR_UpdateScreen ();
671	scr_drawloading = false;
672
673	scr_disabled_for_loading = true;
674	scr_disabled_time = realtime;
675	scr_fullupdate = 0;
676}
677
678/*
679===============
680SCR_EndLoadingPlaque
681
682================
683*/
684void SCR_EndLoadingPlaque (void)
685{
686	scr_disabled_for_loading = false;
687	scr_fullupdate = 0;
688	Con_ClearNotify ();
689}
690
691//=============================================================================
692
693const char	*scr_notifystring;
694qboolean	scr_drawdialog;
695
696void SCR_DrawNotifyString (void)
697{
698	const char	*start;
699	int		l;
700	int		j;
701	int		x, y;
702
703	start = scr_notifystring;
704
705	y = (int)(vid.height*0.35);
706
707	do
708	{
709	// scan the width of the line
710		for (l=0 ; l<40 ; l++)
711			if (start[l] == '\n' || !start[l])
712				break;
713		x = (vid.width - l*8)/2;
714		for (j=0 ; j<l ; j++, x+=8)
715			Draw_Character (x, y, start[j]);
716
717		y += 8;
718
719		while (*start && *start != '\n')
720			start++;
721
722		if (!*start)
723			break;
724		start++;		// skip the \n
725	} while (1);
726}
727
728/*
729==================
730SCR_ModalMessage
731
732Displays a text string in the center of the screen and waits for a Y or N
733keypress.
734==================
735*/
736int SCR_ModalMessage (const char *text)
737{
738	if (cls.state == ca_dedicated)
739		return true;
740
741#if 1
742	// On Android we can't do modal key events, so just say "yes"
743	return 1;
744#else
745	scr_notifystring = text;
746
747// draw a fresh screen
748	scr_fullupdate = 0;
749	scr_drawdialog = true;
750	SCR_UpdateScreen ();
751	scr_drawdialog = false;
752
753	S_ClearBuffer ();		// so dma doesn't loop current sound
754
755	do
756	{
757		key_count = -1;		// wait for a key down and up
758		Sys_SendKeyEvents ();
759	} while (key_lastpress != 'y' && key_lastpress != 'n' && key_lastpress != K_ESCAPE);
760
761	scr_fullupdate = 0;
762	SCR_UpdateScreen ();
763
764	return key_lastpress == 'y';
765#endif
766}
767
768
769//=============================================================================
770
771/*
772===============
773SCR_BringDownConsole
774
775Brings the console down and fades the palettes back to normal
776================
777*/
778void SCR_BringDownConsole (void)
779{
780	int		i;
781
782	scr_centertime_off = 0;
783
784	for (i=0 ; i<20 && scr_conlines != scr_con_current ; i++)
785		SCR_UpdateScreen ();
786
787	cl.cshifts[0].percent = 0;		// no area contents palette on next frame
788	VID_SetPalette (host_basepal);
789}
790
791void SCR_TileClear (void)
792{
793	if (r_refdef.vrect.x > 0) {
794		// left
795		Draw_TileClear (0, 0, r_refdef.vrect.x, vid.height - sb_lines);
796		// right
797		Draw_TileClear (r_refdef.vrect.x + r_refdef.vrect.width, 0,
798			vid.width - r_refdef.vrect.x + r_refdef.vrect.width,
799			vid.height - sb_lines);
800	}
801	if (r_refdef.vrect.y > 0) {
802		// top
803		Draw_TileClear (r_refdef.vrect.x, 0,
804			r_refdef.vrect.x + r_refdef.vrect.width,
805			r_refdef.vrect.y);
806		// bottom
807		Draw_TileClear (r_refdef.vrect.x,
808			r_refdef.vrect.y + r_refdef.vrect.height,
809			r_refdef.vrect.width,
810			vid.height - sb_lines -
811			(r_refdef.vrect.height + r_refdef.vrect.y));
812	}
813}
814
815/*
816==================
817SCR_UpdateScreen
818
819This is called every frame, and can also be called explicitly to flush
820text to the screen.
821
822WARNING: be very careful calling this from elsewhere, because the refresh
823needs almost the entire 256k of stack space!
824==================
825*/
826void SCR_UpdateScreen (void)
827{
828	static float	oldscr_viewsize;
829	vrect_t		vrect;
830
831	if (block_drawing)
832		return;
833
834	vid.numpages = (int)(2 + gl_triplebuffer.value);
835
836	scr_copytop = 0;
837	scr_copyeverything = 0;
838
839	if (scr_disabled_for_loading)
840	{
841		if (realtime - scr_disabled_time > 60)
842		{
843			scr_disabled_for_loading = false;
844			Con_Printf ("load failed.\n");
845		}
846		else
847			return;
848	}
849
850	if (!scr_initialized || !con_initialized)
851		return;				// not initialized yet
852
853
854	GL_BeginRendering (&glx, &gly, &glwidth, &glheight);
855
856	//
857	// determine size of refresh window
858	//
859	if (oldfov != scr_fov.value)
860	{
861		oldfov = scr_fov.value;
862		vid.recalc_refdef = true;
863	}
864
865	if (oldscreensize != scr_viewsize.value)
866	{
867		oldscreensize = scr_viewsize.value;
868		vid.recalc_refdef = true;
869	}
870
871	if (vid.recalc_refdef)
872		SCR_CalcRefdef ();
873
874//
875// do 3D refresh drawing, and then update the screen
876//
877	SCR_SetUpToDrawConsole ();
878
879	V_RenderView ();
880
881	GL_Set2D ();
882
883	//
884	// draw any areas not covered by the refresh
885	//
886	SCR_TileClear ();
887
888	if (scr_drawdialog)
889	{
890		Sbar_Draw ();
891		Draw_FadeScreen ();
892		SCR_DrawNotifyString ();
893		scr_copyeverything = true;
894	}
895	else if (scr_drawloading)
896	{
897		SCR_DrawLoading ();
898		Sbar_Draw ();
899	}
900	else if (cl.intermission == 1 && key_dest == key_game)
901	{
902		Sbar_IntermissionOverlay ();
903	}
904	else if (cl.intermission == 2 && key_dest == key_game)
905	{
906		Sbar_FinaleOverlay ();
907		SCR_CheckDrawCenterString ();
908	}
909	else
910	{
911		if (crosshair.value)
912			Draw_Character (scr_vrect.x + scr_vrect.width/2, scr_vrect.y + scr_vrect.height/2, '+');
913
914		SCR_DrawRam ();
915		SCR_DrawNet ();
916		SCR_DrawTurtle ();
917		SCR_DrawPause ();
918		SCR_CheckDrawCenterString ();
919		Sbar_Draw ();
920		SCR_DrawConsole ();
921		M_Draw ();
922	}
923
924	V_UpdatePalette ();
925
926	GL_EndRendering ();
927}
928