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