#include "quakedef.h" static float scr_conlines; // lines of console to display static float oldfov; static cvar_t scr_conspeed = {"scr_conspeed", "900", true}; static cvar_t scr_centertime = {"scr_centertime", "2"}; static cvar_t scr_showram = {"showram", "1"}; static cvar_t scr_showturtle = {"showturtle", "0"}; static cvar_t scr_showpause = {"showpause", "1"}; static cvar_t scr_printspeed = {"scr_printspeed", "8"}; static cvar_t scr_showfps = {"showfps", "0", true}; static cvar_t v_maxfps = {"v_maxfps", "0", true}; cvar_t scr_viewsize = {"viewsize", "100", true}; cvar_t scr_fov = {"fov", "90", true}; // 10 - 170 static bool scr_initialized; // ready to draw static qpic_t *scr_net, *scr_turtle; static int clearconsole; static bool scr_drawloading; static double scr_disabled_time; static u64int scr_framecount; double scr_frametime, scr_oldtime; int scr_fullupdate; int clearnotify; viddef_t vid; // global video state vrect_t scr_vrect; bool scr_disabled_for_loading; float scr_con_current; float scr_centertime_off; static char scr_centerstring[1024]; static float scr_centertime_start; // for slow victory printing static int scr_center_lines; static int scr_erase_lines; static int scr_erase_center; static const char *scr_notifystring; /* * Called for important messages that should stay in the center of the screen * for a few moments */ void SCR_CenterPrint(const 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++; } } static 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; Draw_TileClear(0, y, vid.width, 8 * scr_erase_lines); } static void SCR_DrawCenterString(void) { const char *start; int l, j, x, y, remaining; // the finale prints the characters one at a time if(cl.intermission) remaining = scr_printspeed.value * (cl.time - scr_centertime_start); else remaining = Q_MAXINT; 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_Character(x, y, start[j]); if(!remaining--) return; } y += 8; while(*start && *start != '\n') start++; if(!*start) break; start++; // skip the \n } while(1); } static void SCR_CheckDrawCenterString(void) { if(scr_center_lines > scr_erase_lines) scr_erase_lines = scr_center_lines; scr_centertime_off -= scr_frametime; if(scr_centertime_off <= 0 && !cl.intermission) return; if(key_dest != key_game) return; SCR_DrawCenterString(); } static float CalcFov(float fov, float a, float b) { float v; fov = clamp(fov, 1, 179); v = a / tanf(fov / 360 * M_PI); return atanf(b / v) * 360.0 / M_PI; } /* Must be called whenever vid changes */ static void SCR_CalcRefdef(void) { vrect_t vrect; float size; scr_fullupdate = 0; // force a background redraw vid.recalc_refdef = false; // bound viewsize if(scr_viewsize.value < 100) setcvar("viewsize", "100"); else if(scr_viewsize.value > 120) setcvar("viewsize", "120"); // bound field of view if(scr_fov.value < 10) setcvar("fov", "10"); else if(scr_fov.value > 170) setcvar("fov", "170"); scr_vrect = r_refdef.vrect; // intermission is always full screen size = cl.intermission ? 120 : scr_viewsize.value; 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; // adapt 4:3 fov to current aspect (taken from yquake2) r_refdef.fov_x = atanf( tanf(scr_fov.value / 360.0 * M_PI) * ((float)r_refdef.vrect.width / (float)r_refdef.vrect.height * 0.75) ) / M_PI * 360.0; r_refdef.fov_y = CalcFov(r_refdef.fov_x, r_refdef.vrect.width, r_refdef.vrect.height); // 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; // 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, vid.aspect); } static void SCR_SizeUp_f(cmd_t *c) { USED(c); setcvarv("viewsize", scr_viewsize.value + 10); vid.recalc_refdef = true; } static void SCR_SizeDown_f(cmd_t *c) { USED(c); setcvarv("viewsize", scr_viewsize.value - 10); vid.recalc_refdef = true; } void SCR_Init(void) { Cvar_RegisterVariable(&scr_fov); Cvar_RegisterVariable(&scr_viewsize); Cvar_RegisterVariable(&scr_conspeed); Cvar_RegisterVariable(&scr_showram); Cvar_RegisterVariable(&scr_showturtle); Cvar_RegisterVariable(&scr_showpause); Cvar_RegisterVariable(&scr_centertime); Cvar_RegisterVariable(&scr_printspeed); Cvar_RegisterVariable(&scr_showfps); Cvar_RegisterVariable(&v_maxfps); Cmd_AddCommand("sizeup", SCR_SizeUp_f); Cmd_AddCommand("sizedown", SCR_SizeDown_f); scr_net = Draw_PicFromWad("net"); scr_turtle = Draw_PicFromWad("turtle"); scr_initialized = true; } static void SCR_DrawTurtle(void) { static int count; if(!scr_showturtle.value) return; if(scr_frametime < 0.1) { count = 0; return; } count++; if(count < 3) return; Draw_Pic(scr_vrect.x, scr_vrect.y, scr_turtle); } static void SCR_DrawFPS(void) { static u64int lastframetime; static int lastcnt, fps; u64int t; int n; char s[16]; if(!scr_showfps.value) return; t = nanosec(); if((t - lastframetime) >= 1000000000ULL) { fps = scr_framecount - lastcnt; lastcnt = scr_framecount; lastframetime = t; } n = snprint(s, sizeof(s), "%d", fps); Draw_String(vid.width - n * 8, 0, s); } static 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); } static void SCR_DrawPause(void) { qpic_t *pic; if(!scr_showpause.value) // 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); } static 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); } static void SCR_SetUpToDrawConsole(void) { float dt; Con_CheckResize(); if(scr_drawloading) return; // never a console with loading plaque // decide on the height of the console con_forcedup = !cl.worldmodel || cls.signon != SIGNONS; if(con_forcedup) { 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 dt = scr_conspeed.value * scr_frametime * vid.height / MINHEIGHT; if(scr_conlines < scr_con_current) { scr_con_current -= dt; if(scr_conlines > scr_con_current) scr_con_current = scr_conlines; } else if(scr_conlines > scr_con_current) { scr_con_current += dt; if(scr_conlines < scr_con_current) scr_con_current = scr_conlines; } if(clearconsole++ < vid.numpages) Draw_TileClear(0, (int)scr_con_current, vid.width, vid.height - (int)scr_con_current); else if(clearnotify++ < vid.numpages) Draw_TileClear(0, 0, vid.width, con_notifylines); else con_notifylines = 0; } static void SCR_DrawConsole(void) { if(scr_con_current) Con_DrawConsole(scr_con_current, true); else if(key_dest == key_game || key_dest == key_message) Con_DrawNotify(); // only draw notify in game clearconsole = 0; } void SCR_BeginLoadingPlaque(void) { stopallsfx(nil); 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; SCR_UpdateScreen(false); scr_drawloading = false; scr_disabled_for_loading = true; scr_disabled_time = realtime; scr_fullupdate = 0; } void SCR_EndLoadingPlaque(void) { scr_disabled_for_loading = false; scr_fullupdate = 0; Con_ClearNotify(); } static void SCR_DrawNotifyString(void) { const char *start; int l, j, 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_Character(x, y, start[j]); y += 8; while(*start && *start != '\n') start++; if(!*start) break; start++; // skip the \n } while(1); } /* * Displays a text string in the center of the screen and waits for a Y or N keypress. */ bool SCR_ModalMessage(const char *text) { if(cls.state == ca_dedicated) return true; scr_notifystring = text; // draw a fresh screen scr_fullupdate = 0; SCR_UpdateScreen(true); do { key_count = -1; // wait for a key down and up Sys_SendKeyEvents(); } while(key_lastpress != 'y' && key_lastpress != 'n' && key_lastpress != K_ESCAPE); scr_fullupdate = 0; SCR_UpdateScreen(false); return key_lastpress == 'y'; } /* 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(false); cl.cshifts[0].percent = 0; // no area contents palette on next frame } static bool boundfps(void) { static double oldtime; double time, fpsmax = v_maxfps.value; if(fpsmax < 1 || fpsmax > 999) return false; time = dtime(); if(time - oldtime < 1.0 / fpsmax) return true; oldtime = time; return false; } /* * This is called every frame, and can also be called explicitly to flush * text to the screen. */ void SCR_UpdateScreen(bool drawdialog) { double t; 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(boundfps()) return; t = dtime(); scr_frametime = t - scr_oldtime; scr_oldtime = t; // check for vid changes if(oldfov != scr_fov.value) { oldfov = scr_fov.value; 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 if(scr_fullupdate++ < vid.numpages) // clear the entire screen Draw_TileClear(0, 0, vid.width, vid.height); SCR_SetUpToDrawConsole(); SCR_EraseCenterString(); V_RenderView(); surfaces = Arr_AllocExtra(surfaces, &r_cnumsurfs, r_outofsurfaces); surf_max = &surfaces[r_cnumsurfs]; r_outofsurfaces = 0; if(r_frame_not_full) { r_frame_not_full = false; return; } if(drawdialog) { Sbar_Draw(); Draw_FadeScreen(); SCR_DrawNotifyString(); } 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_DrawNet(); SCR_DrawTurtle(); SCR_DrawPause(); SCR_CheckDrawCenterString(); Sbar_Draw(); SCR_DrawConsole(); M_Draw(); } SCR_DrawFPS(); V_ApplyShifts(); flipfb(); scr_framecount++; }