1 /*
2 Copyright (C) 1996-1997 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 
21 #include <string.h>
22 
23 #include "client.h"
24 #include "cmd.h"
25 #include "common.h"
26 #include "console.h"
27 #include "draw.h"
28 #include "keys.h"
29 #include "menu.h"
30 #include "quakedef.h"
31 #include "sbar.h"
32 #include "screen.h"
33 #include "sound.h"
34 #include "sys.h"
35 #include "view.h"
36 
37 #include "d_iface.h"
38 #include "r_local.h"
39 
40 #ifdef NQ_HACK
41 #include "host.h"
42 #endif
43 
44 /*
45 
46 background clear
47 rendering
48 turtle/net/ram icons
49 sbar
50 centerprint / slow centerprint
51 notify lines
52 intermission / finale overlay
53 loading plaque
54 console
55 menu
56 
57 required background clears
58 required update regions
59 
60 syncronous draw mode or async
61 One off screen buffer, with updates either copied or xblited
62 Need to double buffer?
63 
64 async draw will require the refresh area to be cleared, because it will be
65 xblited, but sync draw can just ignore it.
66 
67 sync
68 draw
69 
70 CenterPrint();
71 SlowPrint();
72 Screen_Update();
73 Con_Printf();
74 
75 net
76 turn off messages option
77 
78 the refresh is always rendered, unless the console is full screen
79 
80 console is:
81 	notify lines
82 	half
83 	full
84 */
85 
86 static qboolean scr_initialized;	/* ready to draw */
87 
88 // only the refresh window will be updated unless these variables are flagged
89 int scr_copytop;
90 int scr_copyeverything;
91 
92 float scr_con_current;
93 static float scr_conlines;		/* lines of console to display */
94 
95 int scr_fullupdate;
96 static int clearconsole;
97 int clearnotify;
98 
99 vrect_t scr_vrect;
100 
101 qboolean scr_disabled_for_loading;
102 qboolean scr_block_drawing;
103 
104 static cvar_t scr_centertime = { "scr_centertime", "2" };
105 static cvar_t scr_printspeed = { "scr_printspeed", "8" };
106 
107 cvar_t scr_viewsize = { "viewsize", "100", true };
108 cvar_t scr_fov = { "fov", "90" };	// 10 - 170
109 static cvar_t scr_conspeed = { "scr_conspeed", "300" };
110 static vrect_t *pconupdate;
111 qboolean scr_skipupdate;
112 
113 static const qpic_t *scr_ram;
114 static const qpic_t *scr_net;
115 
116 static char scr_centerstring[1024];
117 static float scr_centertime_start;	// for slow victory printing
118 float scr_centertime_off;
119 static int scr_center_lines;
120 static int scr_erase_lines;
121 static int scr_erase_center;
122 
123 #ifdef NQ_HACK
124 static qboolean scr_drawloading;
125 static float scr_disabled_time;
126 #endif
127 #ifdef QW_HACK
128 static float oldsbar;
129 static cvar_t scr_allowsnap = { "scr_allowsnap", "1" };
130 #endif
131 
132 
133 //=============================================================================
134 
135 /*
136 ==============
137 SCR_DrawNet
138 ==============
139 */
140 static void
SCR_DrawNet(void)141 SCR_DrawNet(void)
142 {
143 #ifdef NQ_HACK
144    if (realtime - cl.last_received_message < 0.3)
145       return;
146 #endif
147 #ifdef QW_HACK
148    if (cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged <
149          UPDATE_BACKUP - 1)
150       return;
151 #endif
152 
153    if (cls.demoplayback)
154       return;
155 
156    Draw_Pic(scr_vrect.x + 64, scr_vrect.y, scr_net);
157 }
158 
159 //=============================================================================
160 
161 /*
162 ==================
163 SCR_SetUpToDrawConsole
164 ==================
165 */
SCR_SetUpToDrawConsole(void)166 static void SCR_SetUpToDrawConsole(void)
167 {
168    Con_CheckResize();
169 
170 #ifdef NQ_HACK
171    if (scr_drawloading)
172       return;			// never a console with loading plaque
173 #endif
174 
175    // decide on the height of the console
176 #ifdef NQ_HACK
177    con_forcedup = !cl.worldmodel || cls.state != ca_active;
178 #endif
179 #ifdef QW_HACK
180    con_forcedup = cls.state != ca_active;
181 #endif
182 
183    if (con_forcedup) {
184       scr_conlines = vid.height;	// full screen
185       scr_con_current = scr_conlines;
186    } else if (key_dest == key_console)
187       scr_conlines = vid.height / 2;	// half screen
188    else
189       scr_conlines = 0;	// none visible
190 
191    if (scr_conlines < scr_con_current) {
192       scr_con_current -= scr_conspeed.value * host_frametime;
193       if (scr_conlines > scr_con_current)
194          scr_con_current = scr_conlines;
195 
196    } else if (scr_conlines > scr_con_current) {
197       scr_con_current += scr_conspeed.value * host_frametime;
198       if (scr_conlines < scr_con_current)
199          scr_con_current = scr_conlines;
200    }
201 
202    if (clearconsole++ < vid.numpages) {
203       Sbar_Changed();
204    } else if (clearnotify++ < vid.numpages) {
205       scr_copytop = 1;
206       Draw_TileClear(0, 0, vid.width, con_notifylines);
207    } else
208       con_notifylines = 0;
209 }
210 
211 
212 /*
213 ==================
214 SCR_DrawConsole
215 ==================
216 */
SCR_DrawConsole(void)217 static void SCR_DrawConsole(void)
218 {
219    if (scr_con_current)
220    {
221       scr_copyeverything = 1;
222       Con_DrawConsole(scr_con_current);
223       clearconsole = 0;
224    }
225    else
226    {
227       if (key_dest == key_game || key_dest == key_message)
228          Con_DrawNotify();	// only draw notify in game
229    }
230 }
231 
232 /*
233 ===============================================================================
234 
235 CENTER PRINTING
236 
237 ===============================================================================
238 */
239 
240 /*
241 ==============
242 SCR_CenterPrint
243 
244 Called for important messages that should stay in the center of the screen
245 for a few moments
246 ==============
247 */
248 void
SCR_CenterPrint(const char * str)249 SCR_CenterPrint(const char *str)
250 {
251    strncpy(scr_centerstring, str, sizeof(scr_centerstring));
252    scr_centerstring[sizeof(scr_centerstring) - 1] = 0;
253    scr_centertime_off = scr_centertime.value;
254    scr_centertime_start = cl.time;
255 
256    /* count the number of lines for centering */
257    scr_center_lines = 1;
258    while (*str)
259    {
260       if (*str == '\n')
261          scr_center_lines++;
262       str++;
263    }
264 }
265 
266 static void
SCR_EraseCenterString(void)267 SCR_EraseCenterString(void)
268 {
269     int y, height;
270 
271     if (scr_erase_center++ > vid.numpages) {
272 	scr_erase_lines = 0;
273 	return;
274     }
275 
276     if (scr_center_lines <= 4)
277 	y = vid.height * 0.35;
278     else
279 	y = 48;
280 
281     /* Make sure we don't draw off the bottom of the screen*/
282     height = qmin(8 * scr_erase_lines, ((int)vid.height) - y - 1);
283 
284     scr_copytop = 1;
285     Draw_TileClear(0, y, vid.width, height);
286 }
287 
288 static void
SCR_DrawCenterString(void)289 SCR_DrawCenterString(void)
290 {
291     char *start;
292     int l;
293     int j;
294     int x, y;
295     int remaining;
296 
297     scr_copytop = 1;
298     if (scr_center_lines > scr_erase_lines)
299 	scr_erase_lines = scr_center_lines;
300 
301     scr_centertime_off -= host_frametime;
302 
303     if (scr_centertime_off <= 0 && !cl.intermission)
304 	return;
305     if (key_dest != key_game)
306 	return;
307 
308 // the finale prints the characters one at a time
309     if (cl.intermission)
310 	remaining = scr_printspeed.value * (cl.time - scr_centertime_start);
311     else
312 	remaining = 9999;
313 
314     scr_erase_center = 0;
315     start = scr_centerstring;
316 
317     if (scr_center_lines <= 4)
318 	y = vid.height * 0.35;
319     else
320 	y = 48;
321 
322     do {
323 	// scan the width of the line
324 	for (l = 0; l < 40; l++)
325 	    if (start[l] == '\n' || !start[l])
326 		break;
327 	x = (vid.width - l * 8) / 2;
328 	for (j = 0; j < l; j++, x += 8) {
329 	    Draw_Character(x, y, start[j]);
330 	    if (!remaining--)
331 		return;
332 	}
333 
334 	y += 8;
335 
336 	while (*start && *start != '\n')
337 	    start++;
338 
339 	if (!*start)
340 	    break;
341 	start++;		// skip the \n
342     } while (1);
343 }
344 
345 //=============================================================================
346 
347 static const char *scr_notifystring;
348 static qboolean scr_drawdialog;
349 
350 static void
SCR_DrawNotifyString(void)351 SCR_DrawNotifyString(void)
352 {
353     const char *start;
354     int l;
355     int j;
356     int x, y;
357 
358     start = scr_notifystring;
359 
360     y = vid.height * 0.35;
361 
362     do {
363 	// scan the width of the line
364 	for (l = 0; l < 40; l++)
365 	    if (start[l] == '\n' || !start[l])
366 		break;
367 	x = (vid.width - l * 8) / 2;
368 	for (j = 0; j < l; j++, x += 8)
369 	    Draw_Character(x, y, start[j]);
370 
371 	y += 8;
372 
373 	while (*start && *start != '\n')
374 	    start++;
375 
376 	if (!*start)
377 	    break;
378 	start++;		// skip the \n
379     } while (1);
380 }
381 
382 
383 /*
384 ==================
385 SCR_ModalMessage
386 
387 Displays a text string in the center of the screen and waits for a Y or N
388 keypress.
389 ==================
390 */
391 int
SCR_ModalMessage(const char * text)392 SCR_ModalMessage(const char *text)
393 {
394 #ifdef NQ_HACK
395     if (cls.state == ca_dedicated)
396 	return true;
397 #endif
398 
399     scr_notifystring = text;
400 
401 // draw a fresh screen
402     scr_fullupdate = 0;
403     scr_drawdialog = true;
404     SCR_UpdateScreen();
405     scr_drawdialog = false;
406 
407     S_ClearBuffer();		// so dma doesn't loop current sound
408 
409     do {
410 	key_count = -1;		// wait for a key down and up
411 	Sys_SendKeyEvents();
412     } while (key_lastpress != 'y' && key_lastpress != 'n'
413 	     && key_lastpress != K_ESCAPE);
414 
415     scr_fullupdate = 0;
416     SCR_UpdateScreen();
417 
418     return key_lastpress == 'y';
419 }
420 
421 //============================================================================
422 
423 /*
424 ====================
425 CalcFov
426 ====================
427 */
428 static float
CalcFov(float fov_x,float width,float height)429 CalcFov(float fov_x, float width, float height)
430 {
431     float a;
432     float x;
433 
434     if (fov_x < 1 || fov_x > 179)
435 	Sys_Error("Bad fov: %f", fov_x);
436 
437     x = width / tan(fov_x / 360 * M_PI);
438     a = atan(height / x);
439     a = a * 360 / M_PI;
440 
441     return a;
442 }
443 
444 
445 /*
446 =================
447 SCR_CalcRefdef
448 
449 Must be called whenever vid changes
450 Internal use only
451 =================
452 */
SCR_CalcRefdef(void)453 static void SCR_CalcRefdef(void)
454 {
455    vrect_t vrect;
456    float size;
457 
458    scr_fullupdate = 0;		// force a background redraw
459    vid.recalc_refdef = 0;
460 
461    // force the status bar to redraw
462    Sbar_Changed();
463 
464    //========================================
465 
466    // bound viewsize
467    if (scr_viewsize.value < 30)
468       Cvar_Set("viewsize", "30");
469    if (scr_viewsize.value > 120)
470       Cvar_Set("viewsize", "120");
471 
472    // bound field of view
473    if (scr_fov.value < 10)
474       Cvar_Set("fov", "10");
475    if (scr_fov.value > 170)
476       Cvar_Set("fov", "170");
477 
478    // intermission is always full screen
479    if (cl.intermission)
480       size = 120;
481    else
482       size = scr_viewsize.value;
483 
484    if (size >= 120)
485       sb_lines = 0;		// no status bar at all
486    else if (size >= 110)
487       sb_lines = 24;		// no inventory
488    else
489       sb_lines = 24 + 16 + 8;
490 
491    // these calculations mirror those in R_Init() for r_refdef, but take no
492    // account of water warping
493    vrect.x = 0;
494    vrect.y = 0;
495    vrect.width = vid.width;
496    vrect.height = vid.height;
497 
498    R_SetVrect(&vrect, &scr_vrect, sb_lines);
499 
500    r_refdef.fov_x = scr_fov.value;
501    r_refdef.fov_y =
502       CalcFov(r_refdef.fov_x, r_refdef.vrect.width, r_refdef.vrect.height);
503 
504    // guard against going from one mode to another that's less than half the
505    // vertical resolution
506    if (scr_con_current > vid.height)
507       scr_con_current = vid.height;
508 
509    // notify the refresh of the change
510    R_ViewChanged(&vrect, sb_lines, vid.aspect);
511 }
512 
513 /*
514 =================
515 SCR_SizeUp_f
516 
517 Keybinding command
518 =================
519 */
520 static void
SCR_SizeUp_f(void)521 SCR_SizeUp_f(void)
522 {
523     Cvar_SetValue("viewsize", scr_viewsize.value + 10);
524     vid.recalc_refdef = 1;
525 }
526 
527 
528 /*
529 =================
530 SCR_SizeDown_f
531 
532 Keybinding command
533 =================
534 */
SCR_SizeDown_f(void)535 static void SCR_SizeDown_f(void)
536 {
537    Cvar_SetValue("viewsize", scr_viewsize.value - 10);
538    vid.recalc_refdef = 1;
539 }
540 
541 #ifdef NQ_HACK
542 /*
543 ===============
544 SCR_BeginLoadingPlaque
545 
546 ================
547 */
SCR_BeginLoadingPlaque(void)548 void SCR_BeginLoadingPlaque(void)
549 {
550    S_StopAllSounds(true);
551 
552    if (cls.state != ca_active)
553       return;
554 
555    // redraw with no console and the loading plaque
556    Con_ClearNotify();
557    scr_centertime_off = 0;
558    scr_con_current = 0;
559 
560    scr_drawloading = true;
561    scr_fullupdate = 0;
562    Sbar_Changed();
563    SCR_UpdateScreen();
564    scr_drawloading = false;
565 
566    scr_disabled_for_loading = true;
567    scr_disabled_time = realtime;
568    scr_fullupdate = 0;
569 }
570 
571 /*
572 ==============
573 SCR_DrawLoading
574 ==============
575 */
SCR_DrawLoading(void)576 static void SCR_DrawLoading(void)
577 {
578    const qpic_t *pic;
579 
580    if (!scr_drawloading)
581       return;
582 
583    pic = Draw_CachePic("gfx/loading.lmp");
584    Draw_Pic((vid.width - pic->width) / 2,
585          (vid.height - 48 - pic->height) / 2, pic);
586 }
587 
588 /*
589 ===============
590 SCR_EndLoadingPlaque
591 
592 ================
593 */
SCR_EndLoadingPlaque(void)594 void SCR_EndLoadingPlaque(void)
595 {
596    scr_disabled_for_loading = false;
597    scr_fullupdate = 0;
598    Con_ClearNotify();
599 }
600 #endif /* NQ_HACK */
601 
602 //=============================================================================
603 
604 /*
605 ==================
606 SCR_UpdateScreen
607 
608 This is called every frame, and can also be called explicitly to flush
609 text to the screen.
610 
611 WARNING: be very careful calling this from elsewhere, because the refresh
612 needs almost the entire 256k of stack space!
613 ==================
614 */
615 void
SCR_UpdateScreen(void)616 SCR_UpdateScreen(void)
617 {
618    static float old_viewsize, old_fov;
619    vrect_t vrect;
620 
621    if (scr_skipupdate)
622       return;
623    if (scr_block_drawing)
624       return;
625 
626 #ifdef NQ_HACK
627    if (scr_disabled_for_loading) {
628       /*
629        * FIXME - this really needs to be fixed properly.
630        * Simply starting a new game and typing "changelevel foo" will hang
631        * the engine for 5s (was 60s!) if foo.bsp does not exist.
632        */
633       if (realtime - scr_disabled_time > 5) {
634          scr_disabled_for_loading = false;
635          Con_Printf("load failed.\n");
636       } else
637          return;
638    }
639 #endif
640 #ifdef QW_HACK
641    if (scr_disabled_for_loading)
642       return;
643 #endif
644 
645 #ifdef NQ_HACK
646    if (cls.state == ca_dedicated)
647       return;			// stdout only
648 #endif
649 
650    if (!scr_initialized || !con_initialized)
651       return;			// not initialized yet
652 
653    scr_copytop = 0;
654    scr_copyeverything = 0;
655 
656    /*
657     * Check for vid setting changes
658     */
659    if (old_fov != scr_fov.value) {
660       old_fov = scr_fov.value;
661       vid.recalc_refdef = true;
662    }
663    if (old_viewsize != scr_viewsize.value) {
664       old_viewsize = scr_viewsize.value;
665       vid.recalc_refdef = true;
666    }
667 #ifdef QW_HACK
668    if (oldsbar != cl_sbar.value) {
669       oldsbar = cl_sbar.value;
670       vid.recalc_refdef = true;
671    }
672 #endif
673 
674    if (vid.recalc_refdef)
675       SCR_CalcRefdef();
676 
677    /*
678     * do 3D refresh drawing, and then update the screen
679     */
680 
681    if (scr_fullupdate++ < vid.numpages) {
682       /* clear the entire screen */
683       scr_copyeverything = 1;
684       Draw_TileClear(0, 0, vid.width, vid.height);
685       Sbar_Changed();
686    }
687    pconupdate = NULL;
688    SCR_SetUpToDrawConsole();
689    SCR_EraseCenterString();
690 
691    V_RenderView();
692 
693    if (scr_drawdialog) {
694       Sbar_Draw();
695       Draw_FadeScreen();
696       SCR_DrawNotifyString();
697       scr_copyeverything = true;
698 #ifdef NQ_HACK
699    } else if (scr_drawloading) {
700       SCR_DrawLoading();
701       Sbar_Draw();
702 #endif
703    } else if (cl.intermission == 1 && key_dest == key_game) {
704       Sbar_IntermissionOverlay();
705    } else if (cl.intermission == 2 && key_dest == key_game) {
706       Sbar_FinaleOverlay();
707       SCR_DrawCenterString();
708 #if defined(NQ_HACK) /* FIXME? */
709    } else if (cl.intermission == 3 && key_dest == key_game) {
710       SCR_DrawCenterString();
711 #endif
712    } else {
713       SCR_DrawNet();
714       SCR_DrawCenterString();
715       Sbar_Draw();
716       SCR_DrawConsole();
717       M_Draw();
718    }
719 
720    if (pconupdate)
721       D_UpdateRects(pconupdate);
722 
723    V_UpdatePalette();
724 
725    /*
726     * update one of three areas
727     */
728    if (scr_copyeverything)
729    {
730       vrect.x = 0;
731       vrect.y = 0;
732       vrect.width = vid.width;
733       vrect.height = vid.height;
734    }
735    else if (scr_copytop)
736    {
737       vrect.x = 0;
738       vrect.y = 0;
739       vrect.width = vid.width;
740       vrect.height = vid.height - sb_lines;
741    }
742    else
743    {
744       vrect.x = scr_vrect.x;
745       vrect.y = scr_vrect.y;
746       vrect.width = scr_vrect.width;
747       vrect.height = scr_vrect.height;
748    }
749    vrect.pnext = 0;
750    VID_Update(&vrect);
751 }
752 
753 //=============================================================================
754 
755 /*
756 ==================
757 SCR_Init
758 ==================
759 */
760 void
SCR_Init(void)761 SCR_Init(void)
762 {
763     Cvar_RegisterVariable(&scr_fov);
764     Cvar_RegisterVariable(&scr_viewsize);
765     Cvar_RegisterVariable(&scr_conspeed);
766     Cvar_RegisterVariable(&scr_centertime);
767     Cvar_RegisterVariable(&scr_printspeed);
768 
769     Cmd_AddCommand("sizeup", SCR_SizeUp_f);
770     Cmd_AddCommand("sizedown", SCR_SizeDown_f);
771 
772 #ifdef NQ_HACK
773     scr_ram = (qpic_t*)Draw_PicFromWad("ram");
774     scr_net = (qpic_t*)Draw_PicFromWad("net");
775 #endif
776 #ifdef QW_HACK
777     scr_ram = W_GetLumpName("ram");
778     scr_net = W_GetLumpName("net");
779 
780     Cvar_RegisterVariable(&scr_allowsnap);
781 #endif
782 
783     scr_initialized = true;
784 }
785