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, Boston, MA  02111-1307, USA.
18 
19 */
20 // cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc
21 
22 /*
23 
24   full screen console
25   put up loading plaque
26   blanked background with loading plaque
27   blanked background with menu
28   cinematics
29   full screen image for quit and victory
30 
31   end of unit intermissions
32 
33   */
34 
35 #include "client.h"
36 
37 float		scr_con_current;	// aproaches scr_conlines at scr_conspeed
38 float		scr_conlines;		// 0.0 to 1.0 lines of console to display
39 
40 qboolean	scr_initialized;		// ready to draw
41 
42 int			scr_draw_loading;
43 
44 vrect_t		scr_vrect;		// position of render window on screen
45 
46 
47 cvar_t		*scr_viewsize;
48 cvar_t		*scr_conspeed;
49 cvar_t		*scr_centertime;
50 cvar_t		*scr_showturtle;
51 cvar_t		*scr_showpause;
52 cvar_t		*scr_printspeed;
53 
54 cvar_t		*scr_netgraph;
55 cvar_t		*scr_timegraph;
56 cvar_t		*scr_debuggraph;
57 cvar_t		*scr_graphheight;
58 cvar_t		*scr_graphscale;
59 cvar_t		*scr_graphshift;
60 cvar_t		*scr_drawall;
61 
62 typedef struct
63 {
64 	int		x1, y1, x2, y2;
65 } dirty_t;
66 
67 dirty_t		scr_dirty, scr_old_dirty[2];
68 
69 char		crosshair_pic[MAX_QPATH];
70 int			crosshair_width, crosshair_height;
71 
72 extern cvar_t *cl_drawfps; // FPS hack
73 
74 void SCR_TimeRefresh_f (void);
75 void SCR_Loading_f (void);
76 
77 #ifdef QMAX
78 char		crosshair_pic[MAX_QPATH];
79 int			crosshair_width, crosshair_height;
80 #endif
81 
82 
83 /*
84 ===============================================================================
85 
86 BAR GRAPHS
87 
88 ===============================================================================
89 */
90 
91 /*
92 ==============
93 CL_AddNetgraph
94 
95 A new packet was just parsed
96 ==============
97 */
CL_AddNetgraph(void)98 void CL_AddNetgraph (void)
99 {
100 	int		i;
101 	int		in;
102 	int		ping;
103 
104 	// if using the debuggraph for something else, don't
105 	// add the net lines
106 	if (scr_debuggraph->value || scr_timegraph->value)
107 		return;
108 
109 	for (i=0 ; i<cls.netchan.dropped ; i++)
110 		SCR_DebugGraph (30, 0x40);
111 
112 	for (i=0 ; i<cl.surpressCount ; i++)
113 		SCR_DebugGraph (30, 0xdf);
114 
115 	// see what the latency was on this packet
116 	in = cls.netchan.incoming_acknowledged & (CMD_BACKUP-1);
117 	ping = cls.realtime - cl.cmd_time[in];
118 	ping /= 30;
119 	if (ping > 30)
120 		ping = 30;
121 	SCR_DebugGraph (ping, 0xd0);
122 }
123 
124 
125 typedef struct
126 {
127 	float	value;
128 	int		color;
129 } graphsamp_t;
130 
131 static	int			current;
132 static	graphsamp_t	values[1024];
133 
134 /*
135 ==============
136 SCR_DebugGraph
137 ==============
138 */
SCR_DebugGraph(float value,int color)139 void SCR_DebugGraph (float value, int color)
140 {
141 	values[current&1023].value = value;
142 	values[current&1023].color = color;
143 	current++;
144 }
145 
146 /*
147 ==============
148 SCR_DrawDebugGraph
149 ==============
150 */
SCR_DrawDebugGraph(void)151 void SCR_DrawDebugGraph (void)
152 {
153 	int		a, x, y, w, i, h;
154 	float	v;
155 	int		color;
156 
157 	//
158 	// draw the graph
159 	//
160 	w = scr_vrect.width;
161 
162 	x = scr_vrect.x;
163 	y = scr_vrect.y+scr_vrect.height;
164 	re.DrawFill (x, y-scr_graphheight->value,
165 		w, scr_graphheight->value, 8);
166 
167 	for (a=0 ; a<w ; a++)
168 	{
169 		i = (current-1-a+1024) & 1023;
170 		v = values[i].value;
171 		color = values[i].color;
172 		v = v*scr_graphscale->value + scr_graphshift->value;
173 
174 		if (v < 0)
175 			v += scr_graphheight->value * (1+(int)(-v/scr_graphheight->value));
176 		h = (int)v % (int)scr_graphheight->value;
177 		re.DrawFill (x+w-1-a, y - h, 1,	h, color);
178 	}
179 }
180 
181 /*
182 ===============================================================================
183 
184 CENTER PRINTING
185 
186 ===============================================================================
187 */
188 
189 char		scr_centerstring[1024];
190 float		scr_centertime_start;	// for slow victory printing
191 float		scr_centertime_off;
192 int			scr_center_lines;
193 int			scr_erase_center;
194 
195 /*
196 ==============
197 SCR_CenterPrint
198 
199 Called for important messages that should stay in the center of the screen
200 for a few moments
201 ==============
202 */
SCR_CenterPrint(char * str)203 void SCR_CenterPrint (char *str)
204 {
205 	char	*s;
206 	char	line[64];
207 	int		i, j, l;
208 
209 	strncpy (scr_centerstring, str, sizeof(scr_centerstring)-1);
210 	scr_centertime_off = scr_centertime->value;
211 	scr_centertime_start = cl.time;
212 
213 	// count the number of lines for centering
214 	scr_center_lines = 1;
215 	s = str;
216 	while (*s)
217 	{
218 		if (*s == '\n')
219 			scr_center_lines++;
220 		s++;
221 	}
222 
223 	// echo it to the console
224 	Com_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\n");
225 
226 	s = str;
227 	do
228 	{
229 	// scan the width of the line
230 		for (l=0 ; l<40 ; l++)
231 			if (s[l] == '\n' || !s[l])
232 				break;
233 		for (i=0 ; i<(40-l)/2 ; i++)
234 			line[i] = ' ';
235 
236 		for (j=0 ; j<l ; j++)
237 		{
238 			line[i++] = s[j];
239 		}
240 
241 		line[i] = '\n';
242 		line[i+1] = 0;
243 
244 		Com_Printf ("%s", line);
245 
246 		while (*s && *s != '\n')
247 			s++;
248 
249 		if (!*s)
250 			break;
251 		s++;		// skip the \n
252 	} while (1);
253 	Com_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\n");
254 	Con_ClearNotify ();
255 }
256 
257 
SCR_DrawCenterString(void)258 void SCR_DrawCenterString (void)
259 {
260 	char	*start;
261 	int		l;
262 	int		j;
263 	int		x, y;
264 	int		remaining;
265 
266 // the finale prints the characters one at a time
267 	remaining = 9999;
268 
269 	scr_erase_center = 0;
270 	start = scr_centerstring;
271 
272 	if (scr_center_lines <= 4)
273 		y = viddef.height*0.35;
274 	else
275 		y = 48;
276 
277 	do
278 	{
279 	// scan the width of the line
280 		for (l=0 ; l<40 ; l++)
281 			if (start[l] == '\n' || !start[l])
282 				break;
283 		x = (viddef.width - l*8)/2;
284 		SCR_AddDirtyPoint (x, y);
285 		for (j=0 ; j<l ; j++, x+=8)
286 		{
287 #ifdef QMAX
288 			re.DrawChar (x, y, start[j], 256);
289 #else
290 			re.DrawChar (x, y, start[j]);
291 #endif
292 			if (!remaining--)
293 				return;
294 		}
295 		SCR_AddDirtyPoint (x, y+8);
296 
297 		y += 8;
298 
299 		while (*start && *start != '\n')
300 			start++;
301 
302 		if (!*start)
303 			break;
304 		start++;		// skip the \n
305 	} while (1);
306 }
307 
SCR_CheckDrawCenterString(void)308 void SCR_CheckDrawCenterString (void)
309 {
310 	scr_centertime_off -= cls.frametime;
311 
312 	if (scr_centertime_off <= 0)
313 		return;
314 
315 	SCR_DrawCenterString ();
316 }
317 
318 //=============================================================================
319 
320 /*
321 =================
322 SCR_CalcVrect
323 
324 Sets scr_vrect, the coordinates of the rendered window
325 =================
326 */
SCR_CalcVrect(void)327 static void SCR_CalcVrect (void)
328 {
329 	int		size;
330 
331 	// bound viewsize
332 	if (scr_viewsize->value < 40)
333 		Cvar_Set ("viewsize","40");
334 	if (scr_viewsize->value > 100)
335 		Cvar_Set ("viewsize","100");
336 
337 	size = scr_viewsize->value;
338 
339 	scr_vrect.width = viddef.width*size/100;
340 	scr_vrect.width &= ~7;
341 
342 	scr_vrect.height = viddef.height*size/100;
343 	scr_vrect.height &= ~1;
344 
345 	scr_vrect.x = (viddef.width - scr_vrect.width)/2;
346 	scr_vrect.y = (viddef.height - scr_vrect.height)/2;
347 }
348 
349 
350 /*
351 =================
352 SCR_SizeUp_f
353 
354 Keybinding command
355 =================
356 */
SCR_SizeUp_f(void)357 void SCR_SizeUp_f (void)
358 {
359 	Cvar_SetValue ("viewsize",scr_viewsize->value+10);
360 }
361 
362 
363 /*
364 =================
365 SCR_SizeDown_f
366 
367 Keybinding command
368 =================
369 */
SCR_SizeDown_f(void)370 void SCR_SizeDown_f (void)
371 {
372 	Cvar_SetValue ("viewsize",scr_viewsize->value-10);
373 }
374 
375 /*
376 =================
377 SCR_Sky_f
378 
379 Set a specific sky and rotation speed
380 =================
381 */
SCR_Sky_f(void)382 void SCR_Sky_f (void)
383 {
384 	float	rotate;
385 	vec3_t	axis;
386 
387 	if (Cmd_Argc() < 2)
388 	{
389 		Com_Printf ("Usage: sky <basename> <rotate> <axis x y z>\n");
390 		return;
391 	}
392 	if (Cmd_Argc() > 2)
393 		rotate = atof(Cmd_Argv(2));
394 	else
395 		rotate = 0;
396 	if (Cmd_Argc() == 6)
397 	{
398 		axis[0] = atof(Cmd_Argv(3));
399 		axis[1] = atof(Cmd_Argv(4));
400 		axis[2] = atof(Cmd_Argv(5));
401 	}
402 	else
403 	{
404 		axis[0] = 0;
405 		axis[1] = 0;
406 		axis[2] = 1;
407 	}
408 
409 	re.SetSky (Cmd_Argv(1), rotate, axis);
410 }
411 
412 //============================================================================
413 
414 /*
415 ==================
416 SCR_Init
417 ==================
418 */
SCR_Init(void)419 void SCR_Init (void)
420 {
421 	scr_viewsize = Cvar_Get ("viewsize", "100", CVAR_ARCHIVE);
422 	scr_conspeed = Cvar_Get ("scr_conspeed", "3", 0);
423 	scr_showturtle = Cvar_Get ("scr_showturtle", "0", 0);
424 	scr_showpause = Cvar_Get ("scr_showpause", "1", 0);
425 	scr_centertime = Cvar_Get ("scr_centertime", "2.5", 0);
426 	scr_printspeed = Cvar_Get ("scr_printspeed", "8", 0);
427 	scr_netgraph = Cvar_Get ("netgraph", "0", 0);
428 	scr_timegraph = Cvar_Get ("timegraph", "0", 0);
429 	scr_debuggraph = Cvar_Get ("debuggraph", "0", 0);
430 	scr_graphheight = Cvar_Get ("graphheight", "32", 0);
431 	scr_graphscale = Cvar_Get ("graphscale", "1", 0);
432 	scr_graphshift = Cvar_Get ("graphshift", "0", 0);
433 	scr_drawall = Cvar_Get ("scr_drawall", "0", 0);
434 
435 //
436 // register our commands
437 //
438 	Cmd_AddCommand ("timerefresh",SCR_TimeRefresh_f);
439 	Cmd_AddCommand ("loading",SCR_Loading_f);
440 	Cmd_AddCommand ("sizeup",SCR_SizeUp_f);
441 	Cmd_AddCommand ("sizedown",SCR_SizeDown_f);
442 	Cmd_AddCommand ("sky",SCR_Sky_f);
443 
444 	scr_initialized = true;
445 }
446 
447 
448 /*
449 ==============
450 SCR_DrawNet
451 ==============
452 */
SCR_DrawNet(void)453 void SCR_DrawNet (void)
454 {
455 	if (cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged
456 		< CMD_BACKUP-1)
457 		return;
458 
459 	re.DrawPic (scr_vrect.x+64, scr_vrect.y, "net");
460 }
461 
462 /*
463 ==============
464 SCR_DrawPause
465 ==============
466 */
SCR_DrawPause(void)467 void SCR_DrawPause (void)
468 {
469 	int		w, h;
470 
471 	if (!scr_showpause->value)		// turn off for screenshots
472 		return;
473 
474 	if (!cl_paused->value)
475 		return;
476 
477 	re.DrawGetPicSize (&w, &h, "pause");
478 	re.DrawPic ((viddef.width-w)/2, viddef.height/2 + 8, "pause");
479 }
480 
481 /*
482 ==============
483 SCR_DrawLoading
484 ==============
485 */
SCR_DrawLoading(void)486 void SCR_DrawLoading (void)
487 {
488 	int		w, h;
489 
490 	if (!scr_draw_loading)
491 		return;
492 
493 	scr_draw_loading = false;
494 	re.DrawGetPicSize (&w, &h, "loading");
495 	re.DrawPic ((viddef.width-w)/2, (viddef.height-h)/2, "loading");
496 }
497 
498 //=============================================================================
499 
500 /*
501 ==================
502 SCR_RunConsole
503 
504 Scroll it up or down
505 ==================
506 */
SCR_RunConsole(void)507 void SCR_RunConsole (void)
508 {
509 // decide on the height of the console
510 	if (cls.key_dest == key_console)
511 		scr_conlines = 0.5;		// half screen
512 	else
513 		scr_conlines = 0;				// none visible
514 
515 	if (scr_conlines < scr_con_current)
516 	{
517 		scr_con_current -= scr_conspeed->value*cls.frametime;
518 		if (scr_conlines > scr_con_current)
519 			scr_con_current = scr_conlines;
520 
521 	}
522 	else if (scr_conlines > scr_con_current)
523 	{
524 		scr_con_current += scr_conspeed->value*cls.frametime;
525 		if (scr_conlines < scr_con_current)
526 			scr_con_current = scr_conlines;
527 	}
528 
529 }
530 
531 /*
532 ==================
533 SCR_DrawConsole
534 ==================
535 */
SCR_DrawConsole(void)536 void SCR_DrawConsole (void)
537 {
538 	Con_CheckResize ();
539 
540 	if (cls.state == ca_disconnected || cls.state == ca_connecting)
541 	{	// forced full screen console
542 		Con_DrawConsole (1.0);
543 		return;
544 	}
545 
546 	if (cls.state != ca_active || !cl.refresh_prepped)
547 	{	// connected, but can't render
548 		Con_DrawConsole (0.5);
549 		re.DrawFill (0, viddef.height/2, viddef.width, viddef.height/2, 0);
550 		return;
551 	}
552 
553 	if (scr_con_current)
554 	{
555 		Con_DrawConsole (scr_con_current);
556 	}
557 	else
558 	{
559 		if (cls.key_dest == key_game || cls.key_dest == key_message)
560 			Con_DrawNotify ();	// only draw notify in game
561 	}
562 }
563 
564 //=============================================================================
565 
566 /*
567 ================
568 SCR_BeginLoadingPlaque
569 ================
570 */
SCR_BeginLoadingPlaque(void)571 void SCR_BeginLoadingPlaque (void)
572 {
573 	S_StopAllSounds ();
574 	cl.sound_prepped = false;		// don't play ambients
575 	CDAudio_Stop ();
576 	if (cls.disable_screen)
577 		return;
578 	if (developer->value)
579 		return;
580 	if (cls.state == ca_disconnected)
581 		return;	// if at console, don't bring up the plaque
582 	if (cls.key_dest == key_console)
583 		return;
584 	if (cl.cinematictime > 0)
585 		scr_draw_loading = 2;	// clear to balack first
586 	else
587 		scr_draw_loading = 1;
588 	SCR_UpdateScreen ();
589 	cls.disable_screen = Sys_Milliseconds ();
590 	cls.disable_servercount = cl.servercount;
591 }
592 
593 /*
594 ================
595 SCR_EndLoadingPlaque
596 ================
597 */
SCR_EndLoadingPlaque(void)598 void SCR_EndLoadingPlaque (void)
599 {
600 	cls.disable_screen = 0;
601 	Con_ClearNotify ();
602 }
603 
604 /*
605 ================
606 SCR_Loading_f
607 ================
608 */
SCR_Loading_f(void)609 void SCR_Loading_f (void)
610 {
611 	SCR_BeginLoadingPlaque ();
612 }
613 
614 /*
615 ================
616 SCR_TimeRefresh_f
617 ================
618 */
entitycmpfnc(const entity_t * a,const entity_t * b)619 int entitycmpfnc( const entity_t *a, const entity_t *b )
620 {
621 	/*
622 	** all other models are sorted by model then skin
623 	*/
624 	if ( a->model == b->model ) {
625 	  return ( ( INT ) a->skin - ( INT ) b->skin );
626 	}
627 	else {
628 	  return ( ( INT ) a->model - ( INT ) b->model );
629 	}
630 }
631 
SCR_TimeRefresh_f(void)632 void SCR_TimeRefresh_f (void)
633 {
634 	int		i;
635 	int		start, stop;
636 	float	time;
637 
638 	if ( cls.state != ca_active )
639 		return;
640 
641 	start = Sys_Milliseconds ();
642 
643 	if (Cmd_Argc() == 2)
644 	{	// run without page flipping
645 		re.BeginFrame( 0 );
646 		for (i=0 ; i<128 ; i++)
647 		{
648 			cl.refdef.viewangles[1] = i/128.0*360.0;
649 			re.RenderFrame (&cl.refdef);
650 		}
651 		re.EndFrame();
652 	}
653 	else
654 	{
655 		for (i=0 ; i<128 ; i++)
656 		{
657 			cl.refdef.viewangles[1] = i/128.0*360.0;
658 
659 			re.BeginFrame( 0 );
660 			re.RenderFrame (&cl.refdef);
661 			re.EndFrame();
662 		}
663 	}
664 
665 	stop = Sys_Milliseconds ();
666 	time = (stop-start)/1000.0;
667 	Com_Printf ("%f seconds (%f fps)\n", time, 128/time);
668 }
669 
670 /*
671 =================
672 SCR_AddDirtyPoint
673 =================
674 */
SCR_AddDirtyPoint(int x,int y)675 void SCR_AddDirtyPoint (int x, int y)
676 {
677 	if (x < scr_dirty.x1)
678 		scr_dirty.x1 = x;
679 	if (x > scr_dirty.x2)
680 		scr_dirty.x2 = x;
681 	if (y < scr_dirty.y1)
682 		scr_dirty.y1 = y;
683 	if (y > scr_dirty.y2)
684 		scr_dirty.y2 = y;
685 }
686 
SCR_DirtyScreen(void)687 void SCR_DirtyScreen (void)
688 {
689 	SCR_AddDirtyPoint (0, 0);
690 	SCR_AddDirtyPoint (viddef.width-1, viddef.height-1);
691 }
692 
693 /*
694 ==============
695 SCR_TileClear
696 
697 Clear any parts of the tiled background that were drawn on last frame
698 ==============
699 */
SCR_TileClear(void)700 void SCR_TileClear (void)
701 {
702 	int		i;
703 	int		top, bottom, left, right;
704 	dirty_t	clear;
705 
706 	if (scr_drawall->value)
707 		SCR_DirtyScreen ();	// for power vr or broken page flippers...
708 
709 	if (scr_con_current == 1.0)
710 		return;		// full screen console
711 	if (scr_viewsize->value == 100)
712 		return;		// full screen rendering
713 	if (cl.cinematictime > 0)
714 		return;		// full screen cinematic
715 
716 	// erase rect will be the union of the past three frames
717 	// so tripple buffering works properly
718 	clear = scr_dirty;
719 	for (i=0 ; i<2 ; i++)
720 	{
721 		if (scr_old_dirty[i].x1 < clear.x1)
722 			clear.x1 = scr_old_dirty[i].x1;
723 		if (scr_old_dirty[i].x2 > clear.x2)
724 			clear.x2 = scr_old_dirty[i].x2;
725 		if (scr_old_dirty[i].y1 < clear.y1)
726 			clear.y1 = scr_old_dirty[i].y1;
727 		if (scr_old_dirty[i].y2 > clear.y2)
728 			clear.y2 = scr_old_dirty[i].y2;
729 	}
730 
731 	scr_old_dirty[1] = scr_old_dirty[0];
732 	scr_old_dirty[0] = scr_dirty;
733 
734 	scr_dirty.x1 = 9999;
735 	scr_dirty.x2 = -9999;
736 	scr_dirty.y1 = 9999;
737 	scr_dirty.y2 = -9999;
738 
739 	// don't bother with anything convered by the console)
740 	top = scr_con_current*viddef.height;
741 	if (top >= clear.y1)
742 		clear.y1 = top;
743 
744 	if (clear.y2 <= clear.y1)
745 		return;		// nothing disturbed
746 
747 	top = scr_vrect.y;
748 	bottom = top + scr_vrect.height-1;
749 	left = scr_vrect.x;
750 	right = left + scr_vrect.width-1;
751 
752 	if (clear.y1 < top)
753 	{	// clear above view screen
754 		i = clear.y2 < top-1 ? clear.y2 : top-1;
755 		re.DrawTileClear (clear.x1 , clear.y1,
756 			clear.x2 - clear.x1 + 1, i - clear.y1+1, "backtile");
757 		clear.y1 = top;
758 	}
759 	if (clear.y2 > bottom)
760 	{	// clear below view screen
761 		i = clear.y1 > bottom+1 ? clear.y1 : bottom+1;
762 		re.DrawTileClear (clear.x1, i,
763 			clear.x2-clear.x1+1, clear.y2-i+1, "backtile");
764 		clear.y2 = bottom;
765 	}
766 	if (clear.x1 < left)
767 	{	// clear left of view screen
768 		i = clear.x2 < left-1 ? clear.x2 : left-1;
769 		re.DrawTileClear (clear.x1, clear.y1,
770 			i-clear.x1+1, clear.y2 - clear.y1 + 1, "backtile");
771 		clear.x1 = left;
772 	}
773 	if (clear.x2 > right)
774 	{	// clear left of view screen
775 		i = clear.x1 > right+1 ? clear.x1 : right+1;
776 		re.DrawTileClear (i, clear.y1,
777 			clear.x2-i+1, clear.y2 - clear.y1 + 1, "backtile");
778 		clear.x2 = right;
779 	}
780 
781 }
782 
783 
784 //===============================================================
785 
786 
787 #define STAT_MINUS		10	// num frame for '-' stats digit
788 char		*sb_nums[2][11] =
789 {
790 	{"num_0", "num_1", "num_2", "num_3", "num_4", "num_5",
791 	"num_6", "num_7", "num_8", "num_9", "num_minus"},
792 	{"anum_0", "anum_1", "anum_2", "anum_3", "anum_4", "anum_5",
793 	"anum_6", "anum_7", "anum_8", "anum_9", "anum_minus"}
794 };
795 
796 #define	ICON_WIDTH	24
797 #define	ICON_HEIGHT	24
798 #define	CHAR_WIDTH	16
799 #define	ICON_SPACE	8
800 
801 
802 
803 /*
804 ================
805 SizeHUDString
806 
807 Allow embedded \n in the string
808 ================
809 */
SizeHUDString(char * string,int * w,int * h)810 void SizeHUDString (char *string, int *w, int *h)
811 {
812 	int		lines, width, current;
813 
814 	lines = 1;
815 	width = 0;
816 
817 	current = 0;
818 	while (*string)
819 	{
820 		if (*string == '\n')
821 		{
822 			lines++;
823 			current = 0;
824 		}
825 		else
826 		{
827 			current++;
828 			if (current > width)
829 				width = current;
830 		}
831 		string++;
832 	}
833 
834 	*w = width * 8;
835 	*h = lines * 8;
836 }
837 
DrawHUDString(char * string,int x,int y,int centerwidth,int xor)838 void DrawHUDString (char *string, int x, int y, int centerwidth, int xor)
839 {
840 	int		margin;
841 	char	line[1024];
842 	int		width;
843 	int		i;
844 
845 	margin = x;
846 
847 	while (*string)
848 	{
849 		// scan out one line of text from the string
850 		width = 0;
851 		while (*string && *string != '\n')
852 			line[width++] = *string++;
853 		line[width] = 0;
854 
855 		if (centerwidth)
856 			x = margin + (centerwidth - width*8)/2;
857 		else
858 			x = margin;
859 		for (i=0 ; i<width ; i++)
860 		{
861 #ifdef QMAX
862 			re.DrawChar (x, y, line[i]^xor, 256);
863 #else
864 			re.DrawChar (x, y, line[i]^xor);
865 #endif
866 			x += 8;
867 		}
868 		if (*string)
869 		{
870 			string++;	// skip the \n
871 			x = margin;
872 			y += 8;
873 		}
874 	}
875 }
876 
877 
878 /*
879 ==============
880 SCR_DrawField
881 ==============
882 */
SCR_DrawField(int x,int y,int color,int width,int value)883 void SCR_DrawField (int x, int y, int color, int width, int value)
884 {
885 	char	num[16], *ptr;
886 	int		l;
887 	int		frame;
888 
889 	if (width < 1)
890 		return;
891 
892 	// draw number string
893 	if (width > 5)
894 		width = 5;
895 
896 	SCR_AddDirtyPoint (x, y);
897 	SCR_AddDirtyPoint (x+width*CHAR_WIDTH+2, y+23);
898 
899 	Com_sprintf (num, sizeof(num), "%i", value);
900 	l = strlen(num);
901 	if (l > width)
902 		l = width;
903 	x += 2 + CHAR_WIDTH*(width - l);
904 
905 	ptr = num;
906 	while (*ptr && l)
907 	{
908 		if (*ptr == '-')
909 			frame = STAT_MINUS;
910 		else
911 			frame = *ptr -'0';
912 
913 		re.DrawPic (x,y,sb_nums[color][frame]);
914 		x += CHAR_WIDTH;
915 		ptr++;
916 		l--;
917 	}
918 }
919 
920 
921 /*
922 ===============
923 SCR_TouchPics
924 
925 Allows rendering code to cache all needed sbar graphics
926 ===============
927 */
SCR_TouchPics(void)928 void SCR_TouchPics (void)
929 {
930 	int		i, j;
931 
932 	for (i=0 ; i<2 ; i++)
933 		for (j=0 ; j<11 ; j++)
934 			re.RegisterPic (sb_nums[i][j]);
935 
936 	if (crosshair->value)
937 	{
938 #ifdef QMAX
939 	  if (crosshair->value > 9)
940 	    crosshair->value = 9;
941 #else
942 	  if (crosshair->value > 3 || crosshair->value < 0)
943 	    crosshair->value = 3;
944 #endif
945 	  Com_sprintf (crosshair_pic, sizeof(crosshair_pic), "ch%i", (int)(crosshair->value));
946 	  re.DrawGetPicSize (&crosshair_width, &crosshair_height, crosshair_pic);
947 	  if (!crosshair_width)
948 	    crosshair_pic[0] = 0;
949 
950 	}
951 }
952 
953 /*
954 ================
955 SCR_ExecuteLayoutString
956 
957 ================
958 */
SCR_ExecuteLayoutString(char * s)959 void SCR_ExecuteLayoutString (char *s)
960 {
961 	int		x, y;
962 	int		value;
963 	char	*token;
964 	int		width;
965 	int		index;
966 	clientinfo_t	*ci;
967 
968 	if (cls.state != ca_active || !cl.refresh_prepped)
969 		return;
970 
971 	if (!s[0])
972 		return;
973 
974 	x = 0;
975 	y = 0;
976 	width = 3;
977 
978 	while (s)
979 	{
980 		token = COM_Parse (&s);
981 		if (!strcmp(token, "xl"))
982 		{
983 			token = COM_Parse (&s);
984 			x = atoi(token);
985 			continue;
986 		}
987 		if (!strcmp(token, "xr"))
988 		{
989 			token = COM_Parse (&s);
990 			x = viddef.width + atoi(token);
991 			continue;
992 		}
993 		if (!strcmp(token, "xv"))
994 		{
995 			token = COM_Parse (&s);
996 			x = viddef.width/2 - 160 + atoi(token);
997 			continue;
998 		}
999 
1000 		if (!strcmp(token, "yt"))
1001 		{
1002 			token = COM_Parse (&s);
1003 			y = atoi(token);
1004 			continue;
1005 		}
1006 		if (!strcmp(token, "yb"))
1007 		{
1008 			token = COM_Parse (&s);
1009 			y = viddef.height + atoi(token);
1010 			continue;
1011 		}
1012 		if (!strcmp(token, "yv"))
1013 		{
1014 			token = COM_Parse (&s);
1015 			y = viddef.height/2 - 120 + atoi(token);
1016 			continue;
1017 		}
1018 
1019 		if (!strcmp(token, "pic"))
1020 		{	// draw a pic from a stat number
1021 			token = COM_Parse (&s);
1022 			value = cl.frame.playerstate.stats[atoi(token)];
1023 			if (value >= MAX_IMAGES)
1024 				Com_Error (ERR_DROP, "Pic >= MAX_IMAGES");
1025 			if (cl.configstrings[CS_IMAGES+value])
1026 			{
1027 				SCR_AddDirtyPoint (x, y);
1028 				SCR_AddDirtyPoint (x+23, y+23);
1029 				re.DrawPic (x, y, cl.configstrings[CS_IMAGES+value]);
1030 			}
1031 			continue;
1032 		}
1033 
1034 		if (!strcmp(token, "client"))
1035 		{	// draw a deathmatch client block
1036 			int		score, ping, time;
1037 
1038 			token = COM_Parse (&s);
1039 			x = viddef.width/2 - 160 + atoi(token);
1040 			token = COM_Parse (&s);
1041 			y = viddef.height/2 - 120 + atoi(token);
1042 			SCR_AddDirtyPoint (x, y);
1043 			SCR_AddDirtyPoint (x+159, y+31);
1044 
1045 			token = COM_Parse (&s);
1046 			value = atoi(token);
1047 			if (value >= MAX_CLIENTS || value < 0)
1048 				Com_Error (ERR_DROP, "client >= MAX_CLIENTS");
1049 			ci = &cl.clientinfo[value];
1050 
1051 			token = COM_Parse (&s);
1052 			score = atoi(token);
1053 
1054 			token = COM_Parse (&s);
1055 			ping = atoi(token);
1056 
1057 			token = COM_Parse (&s);
1058 			time = atoi(token);
1059 
1060 			DrawAltString (x+32, y, ci->name);
1061 			DrawString (x+32, y+8,  "Score: ");
1062 			DrawAltString (x+32+7*8, y+8,  va("%i", score));
1063 			DrawString (x+32, y+16, va("Ping:  %i", ping));
1064 			DrawString (x+32, y+24, va("Time:  %i", time));
1065 
1066 			if (!ci->icon)
1067 				ci = &cl.baseclientinfo;
1068 			re.DrawPic (x, y, ci->iconname);
1069 			continue;
1070 		}
1071 
1072 		if (!strcmp(token, "ctf"))
1073 		{	// draw a ctf client block
1074 			int		score, ping;
1075 			char	block[80];
1076 
1077 			token = COM_Parse (&s);
1078 			x = viddef.width/2 - 160 + atoi(token);
1079 			token = COM_Parse (&s);
1080 			y = viddef.height/2 - 120 + atoi(token);
1081 			SCR_AddDirtyPoint (x, y);
1082 			SCR_AddDirtyPoint (x+159, y+31);
1083 
1084 			token = COM_Parse (&s);
1085 			value = atoi(token);
1086 			if (value >= MAX_CLIENTS || value < 0)
1087 				Com_Error (ERR_DROP, "client >= MAX_CLIENTS");
1088 			ci = &cl.clientinfo[value];
1089 
1090 			token = COM_Parse (&s);
1091 			score = atoi(token);
1092 
1093 			token = COM_Parse (&s);
1094 			ping = atoi(token);
1095 			if (ping > 999)
1096 				ping = 999;
1097 
1098 			sprintf(block, "%3d %3d %-12.12s", score, ping, ci->name);
1099 
1100 			if (value == cl.playernum)
1101 				DrawAltString (x, y, block);
1102 			else
1103 				DrawString (x, y, block);
1104 			continue;
1105 		}
1106 
1107 		if (!strcmp(token, "picn"))
1108 		{	// draw a pic from a name
1109 			token = COM_Parse (&s);
1110 			SCR_AddDirtyPoint (x, y);
1111 			SCR_AddDirtyPoint (x+23, y+23);
1112 			re.DrawPic (x, y, token);
1113 			continue;
1114 		}
1115 
1116 		if (!strcmp(token, "num"))
1117 		{	// draw a number
1118 			token = COM_Parse (&s);
1119 			width = atoi(token);
1120 			token = COM_Parse (&s);
1121 			value = cl.frame.playerstate.stats[atoi(token)];
1122 			SCR_DrawField (x, y, 0, width, value);
1123 			continue;
1124 		}
1125 
1126 		if (!strcmp(token, "hnum"))
1127 		{	// health number
1128 			int		color;
1129 
1130 			width = 3;
1131 			value = cl.frame.playerstate.stats[STAT_HEALTH];
1132 			if (value > 25)
1133 				color = 0;	// green
1134 			else if (value > 0)
1135 				color = (cl.frame.serverframe>>2) & 1;		// flash
1136 			else
1137 				color = 1;
1138 
1139 			if (cl.frame.playerstate.stats[STAT_FLASHES] & 1)
1140 				re.DrawPic (x, y, "field_3");
1141 
1142 			SCR_DrawField (x, y, color, width, value);
1143 			continue;
1144 		}
1145 
1146 		if (!strcmp(token, "anum"))
1147 		{	// ammo number
1148 			int		color;
1149 
1150 			width = 3;
1151 			value = cl.frame.playerstate.stats[STAT_AMMO];
1152 			if (value > 5)
1153 				color = 0;	// green
1154 			else if (value >= 0)
1155 				color = (cl.frame.serverframe>>2) & 1;		// flash
1156 			else
1157 				continue;	// negative number = don't show
1158 
1159 			if (cl.frame.playerstate.stats[STAT_FLASHES] & 4)
1160 				re.DrawPic (x, y, "field_3");
1161 
1162 			SCR_DrawField (x, y, color, width, value);
1163 			continue;
1164 		}
1165 
1166 		if (!strcmp(token, "rnum"))
1167 		{	// armor number
1168 			int		color;
1169 
1170 			width = 3;
1171 			value = cl.frame.playerstate.stats[STAT_ARMOR];
1172 			if (value < 1)
1173 				continue;
1174 
1175 			color = 0;	// green
1176 
1177 			if (cl.frame.playerstate.stats[STAT_FLASHES] & 2)
1178 				re.DrawPic (x, y, "field_3");
1179 
1180 			SCR_DrawField (x, y, color, width, value);
1181 			continue;
1182 		}
1183 
1184 
1185 		if (!strcmp(token, "stat_string"))
1186 		{
1187 			token = COM_Parse (&s);
1188 			index = atoi(token);
1189 			if (index < 0 || index >= MAX_CONFIGSTRINGS)
1190 				Com_Error (ERR_DROP, "Bad stat_string index");
1191 			index = cl.frame.playerstate.stats[index];
1192 			if (index < 0 || index >= MAX_CONFIGSTRINGS)
1193 				Com_Error (ERR_DROP, "Bad stat_string index");
1194 			DrawString (x, y, cl.configstrings[index]);
1195 			continue;
1196 		}
1197 
1198 		if (!strcmp(token, "cstring"))
1199 		{
1200 			token = COM_Parse (&s);
1201 			DrawHUDString (token, x, y, 320, 0);
1202 			continue;
1203 		}
1204 
1205 		if (!strcmp(token, "string"))
1206 		{
1207 			token = COM_Parse (&s);
1208 			DrawString (x, y, token);
1209 			continue;
1210 		}
1211 
1212 		if (!strcmp(token, "cstring2"))
1213 		{
1214 			token = COM_Parse (&s);
1215 			DrawHUDString (token, x, y, 320,0x80);
1216 			continue;
1217 		}
1218 
1219 		if (!strcmp(token, "string2"))
1220 		{
1221 			token = COM_Parse (&s);
1222 			DrawAltString (x, y, token);
1223 			continue;
1224 		}
1225 
1226 		if (!strcmp(token, "if"))
1227 		{	// draw a number
1228 			token = COM_Parse (&s);
1229 			value = cl.frame.playerstate.stats[atoi(token)];
1230 			if (!value)
1231 			{	// skip to endif
1232 				while (s && strcmp(token, "endif") )
1233 				{
1234 					token = COM_Parse (&s);
1235 				}
1236 			}
1237 
1238 			continue;
1239 		}
1240 
1241 
1242 	}
1243 }
1244 
1245 
1246 /*
1247 ================
1248 SCR_DrawStats
1249 
1250 The status bar is a small layout program that
1251 is based on the stats array
1252 ================
1253 */
SCR_DrawStats(void)1254 void SCR_DrawStats (void)
1255 {
1256 	SCR_ExecuteLayoutString (cl.configstrings[CS_STATUSBAR]);
1257 }
1258 
1259 
1260 /*
1261 ================
1262 SCR_DrawLayout
1263 
1264 ================
1265 */
1266 #define	STAT_LAYOUTS		13
1267 
SCR_DrawLayout(void)1268 void SCR_DrawLayout (void)
1269 {
1270 	if (!cl.frame.playerstate.stats[STAT_LAYOUTS])
1271 		return;
1272 	SCR_ExecuteLayoutString (cl.layout);
1273 }
1274 
1275 //=======================================================
1276 
1277 /*
1278 ==================
1279 SCR_UpdateScreen
1280 
1281 This is called every frame, and can also be called explicitly to flush
1282 text to the screen.
1283 ==================
1284 */
SCR_UpdateScreen(void)1285 void SCR_UpdateScreen (void)
1286 {
1287 	int numframes;
1288 	int i;
1289 	float separation[2] = { 0, 0 };
1290 
1291 	// if the screen is disabled (loading plaque is up, or vid mode changing)
1292 	// do nothing at all
1293 	if (cls.disable_screen)
1294 	{
1295 		if (Sys_Milliseconds() - cls.disable_screen > 120000)
1296 		{
1297 			cls.disable_screen = 0;
1298 			Com_Printf ("Loading plaque timed out.\n");
1299 		}
1300 		return;
1301 	}
1302 
1303 	if (!scr_initialized || !con.initialized)
1304 		return;				// not initialized yet
1305 
1306 	/*
1307 	** range check cl_camera_separation so we don't inadvertently fry someone's
1308 	** brain
1309 	*/
1310 #ifdef REDBLUE
1311 	if ( cl_stereo_separation->value > 10.0 )
1312 		Cvar_SetValue( "cl_stereo_separation", 10.0 );
1313 #else
1314 	if ( cl_stereo_separation->value > 1.0 )
1315 		Cvar_SetValue( "cl_stereo_separation", 1.0 );
1316 #endif
1317 	else if ( cl_stereo_separation->value < 0 )
1318 		Cvar_SetValue( "cl_stereo_separation", 0.0 );
1319 
1320 #ifdef REDBLUE
1321 	if ( !cl_stereo->value )
1322 	  Cvar_SetValue( "cl_stereo", 1 );
1323 
1324 	numframes = 2;
1325 	separation[0] = -cl_stereo_separation->value / 2.0;
1326 	separation[1] =  cl_stereo_separation->value / 2.0;
1327 #else
1328 	if ( cl_stereo->value )
1329 	{
1330 		numframes = 2;
1331 		separation[0] = -cl_stereo_separation->value / 2;
1332 		separation[1] =  cl_stereo_separation->value / 2;
1333 	}
1334 	else
1335 	{
1336 		separation[0] = 0;
1337 		separation[1] = 0;
1338 		numframes = 1;
1339 	}
1340 #endif
1341 	for ( i = 0; i < numframes; i++ )
1342 	{
1343 	  re.BeginFrame( separation[i] );
1344 
1345 	  if (scr_draw_loading == 2)
1346 	    {	//  loading plaque over black screen
1347 	      int		w, h;
1348 
1349 	      re.CinematicSetPalette(NULL);
1350 	      scr_draw_loading = false;
1351 	      re.DrawGetPicSize (&w, &h, "loading");
1352 	      re.DrawPic ((viddef.width-w)/2, (viddef.height-h)/2, "loading");
1353 	      //			re.EndFrame();
1354 	      //			return;
1355 	    }
1356 	  // if a cinematic is supposed to be running, handle menus
1357 	  // and console specially
1358 	  else if (cl.cinematictime > 0)
1359 	    {
1360 	      if (cls.key_dest == key_menu)
1361 		{
1362 		  if (cl.cinematicpalette_active)
1363 		    {
1364 		      re.CinematicSetPalette(NULL);
1365 		      cl.cinematicpalette_active = false;
1366 		    }
1367 		  M_Draw ();
1368 		  //				re.EndFrame();
1369 		  //				return;
1370 		}
1371 	      else if (cls.key_dest == key_console)
1372 		{
1373 		  if (cl.cinematicpalette_active)
1374 		    {
1375 		      re.CinematicSetPalette(NULL);
1376 		      cl.cinematicpalette_active = false;
1377 		    }
1378 		  SCR_DrawConsole ();
1379 		  //				re.EndFrame();
1380 		  //				return;
1381 		}
1382 	      else
1383 		{
1384 		  SCR_DrawCinematic();
1385 		  //				re.EndFrame();
1386 		  //				return;
1387 		}
1388 	    }
1389 	  else
1390 	    {
1391 
1392 	      // make sure the game palette is active
1393 	      if (cl.cinematicpalette_active)
1394 		{
1395 		  re.CinematicSetPalette(NULL);
1396 		  cl.cinematicpalette_active = false;
1397 		}
1398 
1399 	      // do 3D refresh drawing, and then update the screen
1400 	      SCR_CalcVrect ();
1401 
1402 	      // clear any dirty part of the background
1403 	      SCR_TileClear ();
1404 
1405 	      V_RenderView ( separation[i] );
1406 
1407 	      SCR_DrawStats ();
1408 	      if (cl.frame.playerstate.stats[STAT_LAYOUTS] & 1)
1409 		SCR_DrawLayout ();
1410 	      if (cl.frame.playerstate.stats[STAT_LAYOUTS] & 2)
1411 		CL_DrawInventory ();
1412 
1413 	      SCR_DrawNet ();
1414 	      SCR_CheckDrawCenterString ();
1415 
1416 	      // FPS counter hack
1417 	      // http://www.quakesrc.org/?Page=tutorials&What=./tutorials/Quake2/misc/fps.txt
1418 	      if (cl_drawfps->value) {
1419 		char s[8];
1420 		sprintf(s,"%3.0ffps", 1/cls.frametime);
1421 		DrawString(viddef.width-64,0,s);
1422 	      }
1423 
1424 	      if (scr_timegraph->value)
1425 		SCR_DebugGraph (cls.frametime*300, 0);
1426 
1427 	      if (scr_debuggraph->value || scr_timegraph->value || scr_netgraph->value)
1428 		SCR_DrawDebugGraph ();
1429 
1430 	      SCR_DrawPause ();
1431 
1432 	      SCR_DrawConsole ();
1433 
1434 	      M_Draw ();
1435 
1436 	      SCR_DrawLoading ();
1437 	    }
1438 	}
1439 	re.EndFrame();
1440 }
1441