/* screen.c @description@ Copyright (C) 1996-1997 Id Software, Inc. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to: Free Software Foundation, Inc. 59 Temple Place - Suite 330 Boston, MA 02111-1307, USA $Id$ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include "r_local.h" #include "cmd.h" #include "screen.h" #include "sbar.h" #include "input.h" #include "view.h" #include "host.h" #include "sys.h" #include "console.h" #include "qendian.h" #include "keys.h" #include "draw.h" // only the refresh window will be updated unless these variables are flagged int scr_copytop; int scr_copyeverything; float scr_con_current; float scr_conlines; // lines of console to display int oldscreensize; float oldfov; int oldsbar; cvar_t *scr_viewsize; cvar_t *scr_fov; cvar_t *scr_conspeed; cvar_t *scr_centertime; cvar_t *scr_showram; cvar_t *scr_showturtle; cvar_t *scr_showpause; cvar_t *scr_printspeed; qboolean scr_initialized; // ready to draw qpic_t *scr_ram; qpic_t *scr_net; qpic_t *scr_turtle; int scr_fullupdate; int clearconsole; int clearnotify; viddef_t vid; // global video state vrect_t *pconupdate; vrect_t scr_vrect; qboolean scr_disabled_for_loading; qboolean scr_drawloading; float scr_disabled_time; qboolean scr_skipupdate; qboolean block_drawing; void SCR_ScreenShot_f (void); void SCR_InitCvars (void) { } /* =============================================================================== CENTER PRINTING =============================================================================== */ char scr_centerstring[1024]; float scr_centertime_start; // for slow victory printing float scr_centertime_off; int scr_center_lines; int scr_erase_lines; int scr_erase_center; /* ============== SCR_CenterPrint Called for important messages that should stay in the center of the screen for a few moments ============== */ void SCR_CenterPrint (char *str) { strncpy (scr_centerstring, str, sizeof (scr_centerstring) - 1); scr_centertime_off = scr_centertime->value; scr_centertime_start = cl.time; // count the number of lines for centering scr_center_lines = 1; while (*str) { if (*str == '\n') scr_center_lines++; str++; } } void SCR_EraseCenterString (void) { int y; if (scr_erase_center++ > vid.numpages) { scr_erase_lines = 0; return; } if (scr_center_lines <= 4) y = vid.height * 0.35; else y = 48; scr_copytop = 1; Draw_TileClear (0, y, vid.width, 8 * scr_erase_lines); } void SCR_DrawCenterString (void) { char *start; int l; int j; int x, y; int remaining; // the finale prints the characters one at a time if (cl.intermission) remaining = scr_printspeed->value * (cl.time - scr_centertime_start); else remaining = 9999; scr_erase_center = 0; start = scr_centerstring; if (scr_center_lines <= 4) y = vid.height * 0.35; else y = 48; do { // scan the width of the line for (l = 0; l < 40; l++) if (start[l] == '\n' || !start[l]) break; x = (vid.width - l * 8) / 2; for (j = 0; j < l; j++, x += 8) { Draw_Character8 (x, y, start[j]); if (!remaining--) return; } y += 8; while (*start && *start != '\n') start++; if (!*start) break; start++; // skip the \n } while (1); } void SCR_CheckDrawCenterString (void) { scr_copytop = 1; if (scr_center_lines > scr_erase_lines) scr_erase_lines = scr_center_lines; scr_centertime_off -= host_frametime; if (scr_centertime_off <= 0 && !cl.intermission) return; if (key_dest != key_game) return; SCR_DrawCenterString (); } //============================================================================= /* ==================== CalcFov ==================== */ float CalcFov (float fov_x, float width, float height) { float a; float x; if (fov_x < 1 || fov_x > 179) Sys_Error ("Bad fov: %f", fov_x); x = width / tan (fov_x / 360 * M_PI); a = atan (height / x); a = a * 360 / M_PI; return a; } /* ================= SCR_CalcRefdef Must be called whenever vid changes Internal use only ================= */ static void SCR_CalcRefdef (void) { vrect_t vrect; int size; scr_fullupdate = 0; // force a background redraw vid.recalc_refdef = 0; // force the status bar to redraw Sbar_Changed (); //======================================== // bound viewsize if (scr_viewsize->int_val < 30) Cvar_Set (scr_viewsize, "30"); if (scr_viewsize->int_val > 120) Cvar_Set (scr_viewsize, "120"); // bound field of view if (scr_fov->value < 10) Cvar_Set (scr_fov, "10"); if (scr_fov->value > 170) Cvar_Set (scr_fov, "170"); r_refdef.fov_x = scr_fov->value; r_refdef.fov_y = CalcFov (r_refdef.fov_x, r_refdef.vrect.width, r_refdef.vrect.height); // intermission is always full screen if (cl.intermission) size = 120; else size = scr_viewsize->int_val; if (size >= 120) sb_lines = 0; // no status bar at all else if (size >= 110) sb_lines = 24; // no inventory else sb_lines = 24 + 16 + 8; // these calculations mirror those in R_Init() for r_refdef, but take no // account of water warping vrect.x = 0; vrect.y = 0; vrect.width = vid.width; vrect.height = vid.height; R_SetVrect (&vrect, &scr_vrect, sb_lines); // guard against going from one mode to another that's less than half the // vertical resolution if (scr_con_current > vid.height) scr_con_current = vid.height; // notify the refresh of the change R_ViewChanged (&vrect, sb_lines, vid.aspect); } /* ================= SCR_SizeUp_f Keybinding command ================= */ void SCR_SizeUp_f (void) { Cvar_SetValue (scr_viewsize, scr_viewsize->int_val + 10); vid.recalc_refdef = 1; } /* ================= SCR_SizeDown_f Keybinding command ================= */ void SCR_SizeDown_f (void) { Cvar_SetValue (scr_viewsize, scr_viewsize->int_val - 10); vid.recalc_refdef = 1; } //============================================================================ /* ================== SCR_Init ================== */ void SCR_Init (void) { scr_fov = Cvar_Get ("fov", "90", CVAR_NONE, "10 - 170"); scr_viewsize = Cvar_Get ("viewsize", "100", CVAR_ARCHIVE, "None"); scr_conspeed = Cvar_Get ("scr_conspeed", "300", CVAR_NONE, "None"); scr_showram = Cvar_Get ("showram", "1", CVAR_NONE, "None"); scr_showturtle = Cvar_Get ("showturtle", "0", CVAR_NONE, "None"); scr_showpause = Cvar_Get ("showpause", "1", CVAR_NONE, "None"); scr_centertime = Cvar_Get ("scr_centertime", "2", CVAR_NONE, "None"); scr_printspeed = Cvar_Get ("scr_printspeed", "8", CVAR_NONE, "None"); // // register our commands // Cmd_AddCommand ("screenshot", SCR_ScreenShot_f, "No Description"); Cmd_AddCommand ("sizeup", SCR_SizeUp_f, "No Description"); Cmd_AddCommand ("sizedown", SCR_SizeDown_f, "No Description"); scr_ram = Draw_PicFromWad ("ram"); scr_net = Draw_PicFromWad ("net"); scr_turtle = Draw_PicFromWad ("turtle"); scr_initialized = true; } /* ============== SCR_DrawRam ============== */ void SCR_DrawRam (void) { if (!scr_showram->int_val) return; if (!r_cache_thrash) return; Draw_Pic (scr_vrect.x + 32, scr_vrect.y, scr_ram); } /* ============== SCR_DrawTurtle ============== */ void SCR_DrawTurtle (void) { static int count; if (!scr_showturtle->int_val) return; if (host_frametime < 0.1) { count = 0; return; } count++; if (count < 3) return; Draw_Pic (scr_vrect.x, scr_vrect.y, scr_turtle); } /* ============== SCR_DrawNet ============== */ void SCR_DrawNet (void) { if (realtime - cl.last_received_message < 0.3) return; if (cls.demoplayback) return; Draw_Pic (scr_vrect.x + 64, scr_vrect.y, scr_net); } void SCR_DrawFPS (void) { extern cvar_t *show_fps; static double lastframetime; double t; extern int fps_count; static int lastfps; int x, y; char st[80]; if (!show_fps->int_val) return; t = Sys_DoubleTime (); if ((t - lastframetime) >= 1.0) { lastfps = fps_count; fps_count = 0; lastframetime = t; } snprintf (st, sizeof (st), "%-3d FPS", lastfps); /* Misty: New trick! (for me) the ? makes this work like a if then else - IE: if cl_hudswap->int_val is not null, do first case, else (else is a : here) do second case. Deek taught me this trick */ x = cl_hudswap->int_val ? vid.width - ((strlen (st) * 8) + 8) : 8; y = vid.height - sb_lines - 8; Draw_String8 (x, y, st); } /* Misty: I like to see the time */ void SCR_DrawTime (void) { extern cvar_t *show_time; int x, y; char st[80]; char local_time[120]; time_t systime; /* any cvar that can take multiple settings must be able to handle abuse. */ if (show_time->int_val <= 0) return; /* actually find the time and set systime to it */ time (&systime); if (show_time->int_val == 1) { /* now set local_time to 24 hour time using hours:minutes format */ strftime (local_time, sizeof (local_time), "%k:%M", localtime (&systime)); } else if (show_time->int_val >= 2) { /* >= is another cvar abuse protector */ strftime (local_time, sizeof (local_time), "%l:%M %P", localtime (&systime)); } /* now actually print it to the screen directly above where show_fps is */ snprintf (st, sizeof (st), "%s", local_time); x = cl_hudswap->int_val ? vid.width - ((strlen (st) * 8) + 8) : 8; y = vid.height - sb_lines - 16; Draw_String8 (x, y, st); } /* ============== DrawPause ============== */ void SCR_DrawPause (void) { qpic_t *pic; if (!scr_showpause->int_val) // turn off for screenshots return; if (!cl.paused) return; pic = Draw_CachePic ("gfx/pause.lmp"); Draw_Pic ((vid.width - pic->width) / 2, (vid.height - 48 - pic->height) / 2, pic); } /* ============== SCR_DrawLoading ============== */ void SCR_DrawLoading (void) { qpic_t *pic; if (!scr_drawloading) return; pic = Draw_CachePic ("gfx/loading.lmp"); Draw_Pic ((vid.width - pic->width) / 2, (vid.height - 48 - pic->height) / 2, pic); } //============================================================================= /* ================== SCR_SetUpToDrawConsole ================== */ void SCR_SetUpToDrawConsole (void) { Con_CheckResize (); if (scr_drawloading) return; // never a console with loading // plaque // decide on the height of the console if (!cl.worldmodel || cls.signon != SIGNONS) { scr_conlines = vid.height; // full screen scr_con_current = scr_conlines; } else if (key_dest == key_console) scr_conlines = vid.height / 2; // half screen else scr_conlines = 0; // none visible if (scr_conlines < scr_con_current) { scr_con_current -= scr_conspeed->value * host_frametime; if (scr_conlines > scr_con_current) scr_con_current = scr_conlines; } else if (scr_conlines > scr_con_current) { scr_con_current += scr_conspeed->value * host_frametime; if (scr_conlines < scr_con_current) scr_con_current = scr_conlines; } if (clearconsole++ < vid.numpages) { scr_copytop = 1; Draw_TileClear (0, (int) scr_con_current, vid.width, vid.height - (int) scr_con_current); Sbar_Changed (); } else if (clearnotify++ < vid.numpages) { scr_copytop = 1; Draw_TileClear (0, 0, vid.width, con_notifylines); } else con_notifylines = 0; } /* ================== SCR_DrawConsole ================== */ void SCR_DrawConsole (void) { if (scr_con_current) { scr_copyeverything = 1; Con_DrawConsole (scr_con_current); // , true); clearconsole = 0; } else { if (key_dest == key_game || key_dest == key_message) Con_DrawNotify (); // only draw notify in game } } /* ============================================================================== SCREEN SHOTS ============================================================================== */ typedef struct { char manufacturer; char version; char encoding; char bits_per_pixel; unsigned short xmin, ymin, xmax, ymax; unsigned short hres, vres; unsigned char palette[48]; char reserved; char color_planes; unsigned short bytes_per_line; unsigned short palette_type; char filler[58]; unsigned char data; // unbounded } pcx_t; /* ============== WritePCXfile ============== */ void WritePCXfile (char *filename, byte * data, int width, int height, int rowbytes, byte * palette) { int i, j, length; pcx_t *pcx; byte *pack; pcx = Hunk_TempAlloc (width * height * 2 + 1000); if (pcx == NULL) { Con_Printf ("SCR_ScreenShot_f: not enough memory\n"); return; } pcx->manufacturer = 0x0a; // PCX id pcx->version = 5; // 256 color pcx->encoding = 1; // uncompressed pcx->bits_per_pixel = 8; // 256 color pcx->xmin = 0; pcx->ymin = 0; pcx->xmax = LittleShort ((short) (width - 1)); pcx->ymax = LittleShort ((short) (height - 1)); pcx->hres = LittleShort ((short) width); pcx->vres = LittleShort ((short) height); memset (pcx->palette, 0, sizeof (pcx->palette)); pcx->color_planes = 1; // chunky image pcx->bytes_per_line = LittleShort ((short) width); pcx->palette_type = LittleShort (2); // not a grey scale memset (pcx->filler, 0, sizeof (pcx->filler)); // pack the image pack = &pcx->data; for (i = 0; i < height; i++) { for (j = 0; j < width; j++) { if ((*data & 0xc0) != 0xc0) *pack++ = *data++; else { *pack++ = 0xc1; *pack++ = *data++; } } data += rowbytes - width; } // write the palette *pack++ = 0x0c; // palette ID byte for (i = 0; i < 768; i++) *pack++ = *palette++; // write output file length = pack - (byte *) pcx; COM_WriteFile (filename, pcx, length); } /* ================== SCR_ScreenShot_f ================== */ void SCR_ScreenShot_f (void) { int i; char pcxname[80]; char checkname[MAX_OSPATH]; // // find a file name to save it to // strcpy (pcxname, "nuq000.pcx"); for (i = 0; i <= 999; i++) { pcxname[3] = i / 100 + '0'; pcxname[4] = i / 10 % 10 + '0'; pcxname[5] = i % 10 + '0'; snprintf (checkname, sizeof (checkname), "%s/%s", com_gamedir, pcxname); if (Sys_FileTime (checkname) == -1) break; // file doesn't exist } if (i == 100) { Con_Printf ("SCR_ScreenShot_f: Couldn't create a PCX file\n"); return; } // // save the pcx file // D_EnableBackBufferAccess (); // enable direct drawing of console // to back // buffer WritePCXfile (pcxname, vid.buffer, vid.width, vid.height, vid.rowbytes, host_basepal); D_DisableBackBufferAccess (); // for adapters that can't stay // mapped in // for linear writes all the time Con_Printf ("Wrote %s\n", pcxname); } //============================================================================= /* =============== SCR_BeginLoadingPlaque ================ */ void SCR_BeginLoadingPlaque (void) { S_StopAllSounds (true); if (cls.state != ca_connected) return; if (cls.signon != SIGNONS) return; // redraw with no console and the loading plaque Con_ClearNotify (); scr_centertime_off = 0; scr_con_current = 0; scr_drawloading = true; scr_fullupdate = 0; Sbar_Changed (); SCR_UpdateScreen (); scr_drawloading = false; scr_disabled_for_loading = true; scr_disabled_time = realtime; scr_fullupdate = 0; } /* =============== SCR_EndLoadingPlaque ================ */ void SCR_EndLoadingPlaque (void) { scr_disabled_for_loading = false; scr_fullupdate = 0; Con_ClearNotify (); } //============================================================================= char *scr_notifystring; qboolean scr_drawdialog; void SCR_DrawNotifyString (void) { char *start; int l; int j; int x, y; start = scr_notifystring; y = vid.height * 0.35; do { // scan the width of the line for (l = 0; l < 40; l++) if (start[l] == '\n' || !start[l]) break; x = (vid.width - l * 8) / 2; for (j = 0; j < l; j++, x += 8) Draw_Character8 (x, y, start[j]); y += 8; while (*start && *start != '\n') start++; if (!*start) break; start++; // skip the \n } while (1); } /* ================== SCR_ModalMessage Displays a text string in the center of the screen and waits for a Y or N keypress. ================== */ int SCR_ModalMessage (char *text) { if (cls.state == ca_dedicated) return true; scr_notifystring = text; // draw a fresh screen scr_fullupdate = 0; scr_drawdialog = true; SCR_UpdateScreen (); scr_drawdialog = false; S_ClearBuffer (); // so dma doesn't loop current sound do { // key_count = -1; // wait for a key down and up IN_SendKeyEvents (); } while (key_lastpress != 'y' && key_lastpress != 'n' && key_lastpress != K_ESCAPE); scr_fullupdate = 0; SCR_UpdateScreen (); return key_lastpress == 'y'; } //============================================================================= /* =============== SCR_BringDownConsole Brings the console down and fades the palettes back to normal ================ */ void SCR_BringDownConsole (void) { int i; scr_centertime_off = 0; for (i = 0; i < 20 && scr_conlines != scr_con_current; i++) SCR_UpdateScreen (); cl.cshifts[0].percent = 0; // no area contents palette on next // frame VID_SetPalette (host_basepal); } /* ================== SCR_UpdateScreen This is called every frame, and can also be called explicitly to flush text to the screen. WARNING: be very careful calling this from elsewhere, because the refresh needs almost the entire 256k of stack space! ================== */ void SCR_UpdateScreen (void) { static int oldscr_viewsize; vrect_t vrect; if (scr_skipupdate || block_drawing) return; scr_copytop = 0; scr_copyeverything = 0; if (scr_disabled_for_loading) { if (realtime - scr_disabled_time > 60) { scr_disabled_for_loading = false; Con_Printf ("load failed.\n"); } else return; } if (cls.state == ca_dedicated) return; // stdout only if (!scr_initialized || !con_initialized) return; // not initialized yet if (scr_viewsize->int_val != oldscr_viewsize) { oldscr_viewsize = scr_viewsize->int_val; vid.recalc_refdef = 1; } // // check for vid changes // if (oldfov != scr_fov->value) { oldfov = scr_fov->value; vid.recalc_refdef = true; } if (oldscreensize != scr_viewsize->int_val) { oldscreensize = scr_viewsize->int_val; vid.recalc_refdef = true; } if (oldsbar != cl_sbar->int_val) { oldsbar = cl_sbar->int_val; vid.recalc_refdef = true; } if (vid.recalc_refdef) { // something changed, so reorder the screen SCR_CalcRefdef (); } // // do 3D refresh drawing, and then update the screen // D_EnableBackBufferAccess (); // of all overlay stuff if drawing // directly if (scr_fullupdate++ < vid.numpages) { // clear the entire screen scr_copyeverything = 1; Draw_TileClear (0, 0, vid.width, vid.height); Sbar_Changed (); } pconupdate = NULL; SCR_SetUpToDrawConsole (); SCR_EraseCenterString (); D_DisableBackBufferAccess (); // for adapters that can't stay // mapped in // for linear writes all the time VID_LockBuffer (); V_RenderView (); VID_UnlockBuffer (); D_EnableBackBufferAccess (); // of all overlay stuff if drawing // directly if (scr_drawdialog) { Sbar_Draw (); Draw_FadeScreen (); SCR_DrawNotifyString (); scr_copyeverything = true; } else if (scr_drawloading) { SCR_DrawLoading (); Sbar_Draw (); } else if (cl.intermission == 1 && key_dest == key_game) { Sbar_IntermissionOverlay (); } else if (cl.intermission == 2 && key_dest == key_game) { Sbar_FinaleOverlay (); SCR_CheckDrawCenterString (); } else if (cl.intermission == 3 && key_dest == key_game) { SCR_CheckDrawCenterString (); } else { SCR_DrawRam (); SCR_DrawNet (); SCR_DrawTurtle (); SCR_DrawPause (); SCR_DrawFPS (); SCR_DrawTime (); SCR_CheckDrawCenterString (); Sbar_Draw (); SCR_DrawConsole (); // FIXME: MENUCODE // M_Draw (); } D_DisableBackBufferAccess (); // for adapters that can't stay // mapped in // for linear writes all the time if (pconupdate) { D_UpdateRects (pconupdate); } V_UpdatePalette (); // // update one of three areas // if (scr_copyeverything) { vrect.x = 0; vrect.y = 0; vrect.width = vid.width; vrect.height = vid.height; vrect.pnext = 0; VID_Update (&vrect); } else if (scr_copytop) { vrect.x = 0; vrect.y = 0; vrect.width = vid.width; vrect.height = vid.height - sb_lines; vrect.pnext = 0; VID_Update (&vrect); } else { vrect.x = scr_vrect.x; vrect.y = scr_vrect.y; vrect.width = scr_vrect.width; vrect.height = scr_vrect.height; vrect.pnext = 0; VID_Update (&vrect); } } /* ================== SCR_UpdateWholeScreen ================== */ void SCR_UpdateWholeScreen (void) { scr_fullupdate = 0; SCR_UpdateScreen (); }