/* * Copyright (C) 1997-2001 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 the Free Software Foundation, Inc., 59 * Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ /* cl_scrn.c -- master for refresh, status bar, console, chat, notify, etc */ /* * full screen console put up loading plaque blanked background with loading * plaque blanked background with menu cinematics full screen image for quit * and victory * * end of unit intermissions * */ #include "client.h" float scr_con_current;/* aproaches scr_conlines at scr_conspeed */ float scr_conlines; /* 0.0 to 1.0 lines of console to display */ qboolean scr_initialized;/* ready to draw */ int scr_draw_loading; vrect_t scr_vrect; /* position of render window on screen */ cvar_t *scr_viewsize; cvar_t *scr_conspeed; cvar_t *scr_centertime; cvar_t *scr_showturtle; cvar_t *scr_showpause; cvar_t *scr_printspeed; cvar_t *scr_netgraph; cvar_t *scr_netgraph_trans; cvar_t *scr_netgraph_image; cvar_t *scr_netgraph_size; cvar_t *scr_netgraph_pos; cvar_t *scr_netgraph_pos_x; cvar_t *scr_netgraph_pos_y; cvar_t *scr_timegraph; cvar_t *scr_debuggraph; cvar_t *scr_graphheight; cvar_t *scr_graphscale; cvar_t *scr_graphshift; cvar_t *scr_drawall; cvar_t *con_height; cvar_t *cl_drawclock; cvar_t *cl_drawtimestamps; cvar_t *cl_drawping; cvar_t *cl_draw_x; cvar_t *cl_draw_y; cvar_t *cl_drawrate; cvar_t *cl_drawmaptime; cvar_t *cl_drawdate; cvar_t *cl_drawhud; cvar_t *cl_drawchathud; #define MAX_SCRN_PINGS 16 int ScrnPings[MAX_SCRN_PINGS]; typedef struct { int x1, y1, x2, y2; } dirty_t; dirty_t scr_dirty, scr_old_dirty[2]; char crosshair_pic[MAX_QPATH]; int crosshair_width, crosshair_height; extern cvar_t *cl_drawfps; /* FPS hack */ void SCR_TimeRefresh_f(void); void SCR_Loading_f(void); extern NiceAss_Chat_t ChatMessages; /* * * ============================================================================ * === * * BAR GRAPHS * * ========== * ==================================================================== */ /* * ============== CL_AddNetgraph * * A new packet was just parsed ============== */ int currentping; void CL_AddNetgraph(void) { int i; int in; int ping; static int ScrnPingsIndex = 0; in = cls.netchan.incoming_acknowledged & (CMD_BACKUP - 1); ping = cls.realtime - cl.cmd_time[in]; currentping = cls.realtime - cl.cmd_time[in]; /* if using the debuggraph for something else, don't add the net lines */ if (scr_debuggraph->value || scr_timegraph->value) return; for (i = 0; i < cls.netchan.dropped; i++) SCR_DebugGraph(30, 0x40); for (i = 0; i < cl.surpressCount; i++) SCR_DebugGraph(30, 0xdf); /* see what the latency was on this packet */ /* in = cls.netchan.incoming_acknowledged & (CMD_BACKUP-1); */ /* ping = cls.realtime - cl.cmd_time[in]; */ /* NiceAss: */ ScrnPings[ScrnPingsIndex % MAX_SCRN_PINGS] = ping; ScrnPingsIndex++; /* ping /= 30; */ ping = currentping / 30; if (ping > 30) ping = 30; SCR_DebugGraph(ping, 0xd0); } typedef struct { float value; int color; } graphsamp_t; static int current; static graphsamp_t values[1024]; /* * ============== SCR_DebugGraph ============== */ void SCR_DebugGraph(float value, int color) { values[current & 1023].value = value; values[current & 1023].color = color; current++; } /* * ============== SCR_DrawDebugGraph ============== * Plus transparency */ void SCR_DrawDebugGraph_Original (void) { int a, x, y, w, i, h; float v; int color; /* draw the graph */ w = scr_vrect.width; x = scr_vrect.x; y = scr_vrect.y+scr_vrect.height; if (!scr_netgraph_trans->value) { re.DrawFill (x, y-scr_graphheight->value, w, scr_graphheight->value, 8); } for (a=0 ; avalue + scr_graphshift->value; if (v < 0) v += scr_graphheight->value * (1+(int)(-v/scr_graphheight->value)); h = (int)v % (int)scr_graphheight->value; re.DrawFill (x+w-1-a, y - h, 1, h, color); } } void SCR_DrawDebugGraph_Box(void) { int a, x, y, w, i, h, min, max; float v, avgs = 0, avgr = 0; int color, time_net; static float lasttime = 0; static int fps, ping; char *netgraph_image; struct tm *now; time_t tnow; char timebuf[32], temp_map[32]; int time_clock, map_time = 0, time_map, hour, mins, secs; time_net = Sys_Milliseconds(); tnow = time((time_t *) 0); now = localtime(&tnow); h = w = scr_netgraph_size->value; if (scr_netgraph_pos->value == 1) { /* bottom right */ x = scr_vrect.width - (w + 2) - 1; y = scr_vrect.height - (h + 2) - 1; } else if (scr_netgraph_pos->value == 2) { /* bottom left */ x = 0; y = scr_vrect.height - (h + 2) - 1; } else if (scr_netgraph_pos->value == 3) { /* top right */ x = scr_vrect.width - (w + 2) - 1; y = 0; } else if (scr_netgraph_pos->value == 4) { /* top left */ x = 0; y = 0; } else { /* custom */ x = scr_netgraph_pos_x->value; y = scr_netgraph_pos_y->value; } netgraph_image = scr_netgraph_image->string; re.DrawStretchPic(x, y, w + 2, h + 2, netgraph_image, scr_netgraph_trans->value); for (a = 0; a < w; a++) { i = (current - 1 - a + 1024) & 1023; v = values[i].value; color = values[i].color; v = v * scr_graphscale->value; if (v < 1) v += h * (1 + (int)(-v / h)); max = (int)v % h + 1; min = y + h - max - scr_graphshift->value; /* bind to box! */ if (min < y + 1) min = y + 1; if (min > y + h) min = y + h; if (min + max > y + h) max = y + h - max; re.DrawFill(x + w - a, min, 1, max, color); } if (cls.realtime - lasttime > 50) { lasttime = cls.realtime; fps = (cls.frametime) ? 1 / cls.frametime : 0; ping = currentping; } for (i = 0; i < MAX_NET_HISTORY; i++) avgs += Net_History.SendsSize[i]; if (Net_History.SendsStartTime) avgs /= (time_net - Net_History.SendsStartTime); avgs *= 1000.0; avgs /= 1024.0; for (i = 0; i < MAX_NET_HISTORY; i++) avgr += Net_History.RecsSize[i]; if (Net_History.RecsStartTime) avgr /= (time_net - Net_History.RecsStartTime); avgr *= 1000.0; avgr /= 1024.0; if (scr_netgraph->value == 3 ) { #if 0 if (cl_drawclock->value == 1) { time_clock = strftime( timebuf, 32, " Time: %H:%M", now ); } else { time_clock = strftime( timebuf, 32, " Time: %I:%M %p", now ); } #else time_clock = strftime( timebuf, 32, " Time: %H:%M", now ); #endif time_map = cl.time / 1000; hour = time_map/3600; mins = (time_map%3600) /60; secs = time_map%60; if (hour > 0) { Com_sprintf(temp_map, sizeof(temp_map), " Map: %i:%02i:%02i", hour, mins, secs); } else { Com_sprintf(temp_map, sizeof(temp_map), " Map: %i:%02i", mins, secs); } DrawString(x, y + 5 * 0, va(" FPS: %3i", fps)); DrawString(x, y + 5 * 1.7, va(" Ping: %3i", ping)); DrawString(x, y + 5 * 3.4, va(" DL: %2.1f", avgr)); DrawString(x, y + 5 * 5.1, va(" UL: %2.1f", avgs)); DrawString(x, y + 5 * 6.8, va(timebuf, time_clock)); DrawString(x, y + 5 * 8.5, va(temp_map, map_time)); } #if 0 /* border... */ re.DrawFill(x, y, (w + 2), 1, 0); re.DrawFill(x, y + (h + 2), (w + 2), 1, 0); re.DrawFill(x, y, 1, (h + 2), 0); re.DrawFill(x + (w + 2), y, 1, (h + 2), 0); #endif } void SCR_DrawDebugGraph(void) { if (scr_netgraph->value == 1) SCR_DrawDebugGraph_Original(); else if (scr_netgraph->value == 2 || scr_netgraph->value == 3) SCR_DrawDebugGraph_Box(); else return; } /* * * ============================================================================ * === * * CENTER PRINTING * * =============== * =============================================================== */ char scr_centerstring[1024]; float scr_centertime_start; /* for slow victory printing */ float scr_centertime_off; float scr_centertime_end; int scr_center_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) { char *s; char line[64]; int i, j, l; Q_strncpyz(scr_centerstring, str, sizeof(scr_centerstring)); scr_centertime_off = scr_centertime->value; scr_centertime_end = scr_centertime_off; scr_centertime_start = cl.time; /* count the number of lines for centering */ scr_center_lines = 1; s = str; while (*s) { if (*s == '\n') scr_center_lines++; s++; } /* echo it to the console */ 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"); s = str; do { /* scan the width of the line */ for (l = 0; l < 40; l++) if (s[l] == '\n' || !s[l]) break; for (i = 0; i < (40 - l) / 2; i++) line[i] = ' '; for (j = 0; j < l; j++) { line[i++] = s[j]; } line[i] = '\n'; line[i + 1] = 0; Com_Printf("%s", line); while (*s && *s != '\n') s++; if (!*s) break; s++; /* skip the \n */ } while (1); 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"); Con_ClearNotify(); } void SCR_DrawCenterString(void) { char *start; int l; int j; int x, y; int remaining; int alpha = 254 * ( 1 - (((cl.time-scr_centertime_start)/1000.0)/(scr_centertime_end))); /* the finale prints the characters one at a time */ remaining = 9999; scr_erase_center = 0; start = scr_centerstring; if (scr_center_lines <= 4) y = viddef.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 = (viddef.width - l * 8) / 2; SCR_AddDirtyPoint(x, y); for (j = 0; j < l; j++, x += 8) { re.DrawChar(x, y, start[j], alpha); if (!remaining--) return; } SCR_AddDirtyPoint(x, y + 8); y += 8; while (*start && *start != '\n') start++; if (!*start) break; start++; /* skip the \n */ } while (1); } void SCR_CheckDrawCenterString(void) { scr_centertime_off -= cls.frametime; if (scr_centertime_off <= 0) return; SCR_DrawCenterString(); } /* * =========================================================================== * == */ /* * ================= SCR_CalcVrect * * Sets scr_vrect, the coordinates of the rendered window ================= */ static void SCR_CalcVrect(void) { int size; /* bound viewsize */ if (scr_viewsize->value < 40) Cvar_Set("viewsize", "40"); if (scr_viewsize->value > 100) Cvar_Set("viewsize", "100"); size = scr_viewsize->value; scr_vrect.width = viddef.width * size / 100; scr_vrect.width &= ~7; scr_vrect.height = viddef.height * size / 100; scr_vrect.height &= ~1; scr_vrect.x = (viddef.width - scr_vrect.width) / 2; scr_vrect.y = (viddef.height - scr_vrect.height) / 2; } /* * ================= SCR_SizeUp_f * * Keybinding command ================= */ void SCR_SizeUp_f(void) { Cvar_SetValue("viewsize", scr_viewsize->value + 10); } /* * ================= SCR_SizeDown_f * * Keybinding command ================= */ void SCR_SizeDown_f(void) { Cvar_SetValue("viewsize", scr_viewsize->value - 10); } /* * ================= SCR_Sky_f * * Set a specific sky and rotation speed ================= */ void SCR_Sky_f(void) { float rotate; vec3_t axis; if (Cmd_Argc() < 2) { Com_Printf("Usage: sky \n"); return; } if (Cmd_Argc() > 2) rotate = atof(Cmd_Argv(2)); else rotate = 0; if (Cmd_Argc() == 6) { axis[0] = atof(Cmd_Argv(3)); axis[1] = atof(Cmd_Argv(4)); axis[2] = atof(Cmd_Argv(5)); } else { axis[0] = 0; axis[1] = 0; axis[2] = 1; } re.SetSky(Cmd_Argv(1), rotate, axis); } /* * =========================================================================== * = */ /* * ================== SCR_Init ================== */ void SCR_Init(void) { scr_viewsize = Cvar_Get("viewsize", "100", CVAR_ARCHIVE); scr_conspeed = Cvar_Get("scr_conspeed", "3", 0); scr_showturtle = Cvar_Get("scr_showturtle", "0", 0); scr_showpause = Cvar_Get("scr_showpause", "1", 0); scr_centertime = Cvar_Get("scr_centertime", "2.5", 0); scr_printspeed = Cvar_Get("scr_printspeed", "8", 0); scr_netgraph = Cvar_Get("netgraph", "3", CVAR_ARCHIVE); scr_netgraph_trans = Cvar_Get("netgraph_trans", "0.3", CVAR_ARCHIVE); scr_netgraph_image = Cvar_Get("netgraph_image", "net", CVAR_ARCHIVE); scr_netgraph_size = Cvar_Get("netgraph_size", "140", CVAR_ARCHIVE); scr_netgraph_pos = Cvar_Get("netgraph_pos", "3", CVAR_ARCHIVE); scr_netgraph_pos_x = Cvar_Get("netgraph_pos_x", "0", CVAR_ARCHIVE); scr_netgraph_pos_y = Cvar_Get("netgraph_pos_y", "0", CVAR_ARCHIVE); scr_timegraph = Cvar_Get("timegraph", "0", 0); scr_debuggraph = Cvar_Get("debuggraph", "0", 0); scr_graphheight = Cvar_Get("graphheight", "32", 0); scr_graphscale = Cvar_Get("graphscale", "1", 0); scr_graphshift = Cvar_Get("graphshift", "0", 0); scr_drawall = Cvar_Get("scr_drawall", "0", 0); con_height = Cvar_Get("con_height", "0.5", CVAR_ARCHIVE); cl_drawping = Cvar_Get("cl_drawping", "0", CVAR_ARCHIVE); cl_draw_x = Cvar_Get("cl_draw_x", "0", CVAR_ARCHIVE); cl_draw_y = Cvar_Get("cl_draw_y", "0.8", CVAR_ARCHIVE); cl_drawrate = Cvar_Get("cl_drawrate", "0", CVAR_ARCHIVE); cl_drawdate = Cvar_Get("cl_drawdate", "0", CVAR_ARCHIVE); cl_drawhud = Cvar_Get("cl_drawhud", "1", 0); cl_drawchathud = Cvar_Get("cl_drawchathud", "1", CVAR_ARCHIVE); // /* register our commands */ // Cmd_AddCommand("timerefresh", SCR_TimeRefresh_f); Cmd_AddCommand("loading", SCR_Loading_f); Cmd_AddCommand("sizeup", SCR_SizeUp_f); Cmd_AddCommand("sizedown", SCR_SizeDown_f); Cmd_AddCommand("sky", SCR_Sky_f); scr_initialized = true; } /* * ============== SCR_DrawNet ============== */ void SCR_DrawNet(void) { if (cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged < CMD_BACKUP - 1) return; re.DrawPic(scr_vrect.x + 64, scr_vrect.y, "net", cl_hudalpha->value); } /* * ============== SCR_DrawPause ============== */ void SCR_DrawPause(void) { int w, h; if (!scr_showpause->value) /* turn off for screenshots */ return; if (!cl_paused->value) return; if (cls.key_dest == key_menu) return; re.DrawGetPicSize(&w, &h, "pause"); re.DrawPic((viddef.width - w) / 2, viddef.height / 2 + 8, "pause", 1.0); } /* * ============== SCR_DrawLoading ============== */ void SCR_DrawLoading(void) { int w, h; if (!scr_draw_loading) return; scr_draw_loading = false; re.DrawGetPicSize(&w, &h, "loading"); re.DrawPic((viddef.width - w) / 2, (viddef.height - h) / 2, "loading", 1.0); } /* * =========================================================================== * == */ /* * ================== SCR_RunConsole * * Scroll it up or down ================== */ void SCR_RunConsole(void) { /* decide on the height of the console */ if (cls.key_dest == key_console) { if (con_height->value < 0.1) Cvar_SetValue("con_height", 0.1); scr_conlines = con_height->value; /* user controllable */ } else scr_conlines = 0; /* none visible */ if (scr_conlines < scr_con_current) { scr_con_current -= scr_conspeed->value * cls.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 * cls.frametime; if (scr_conlines < scr_con_current) scr_con_current = scr_conlines; } } /* * ================== SCR_DrawConsole ================== */ void SCR_DrawConsole(void) { Con_CheckResize(); if (cls.state == ca_disconnected || cls.state == ca_connecting) { /* forced full screen console */ Con_DrawConsole(1.0, false); return; } if (cls.state != ca_active || !cl.refresh_prepped) { /* connected, but can't render */ if (con_height->value < 0.1) Cvar_SetValue("con_height", 0.1); else if (con_height->value > 1) Cvar_SetValue("con_height", 1); Con_DrawConsole(0.5, false); re.DrawFill(0, viddef.height / 2, viddef.width, viddef.height / 2, 0); return; } if (scr_con_current) { Con_DrawConsole(scr_con_current, true); } else { if (cls.key_dest == key_game || cls.key_dest == key_message) Con_DrawNotify(); /* only draw notify in game */ } } /* * =========================================================================== * == */ /* * ================ SCR_BeginLoadingPlaque ================ */ void SCR_BeginLoadingPlaque(void) { S_StopAllSounds(); cl.sound_prepped = false; /* don't play ambients */ CDAudio_Stop(); if (cls.disable_screen) return; if (developer->value) return; if (cls.state == ca_disconnected) return; /* if at console, don't bring up the plaque */ if (cls.key_dest == key_console) return; if (cl.cinematictime > 0) scr_draw_loading = 2; /* clear to balack first */ else scr_draw_loading = 1; SCR_UpdateScreen(); cls.disable_screen = Sys_Milliseconds(); cls.disable_servercount = cl.servercount; } /* * ================ SCR_EndLoadingPlaque ================ */ void SCR_EndLoadingPlaque(void) { cls.disable_screen = 0; Con_ClearNotify(); } /* * ================ SCR_Loading_f ================ */ void SCR_Loading_f(void) { SCR_BeginLoadingPlaque(); } /* * ================ SCR_TimeRefresh_f ================ */ int entitycmpfnc(const entity_t * a, const entity_t * b) { /* * * all other models are sorted by model then skin */ if (a->model == b->model) { return ((INT) a->skin - (INT) b->skin); } else { return ((INT) a->model - (INT) b->model); } } void SCR_TimeRefresh_f(void) { int i; int start, stop; float time; if (cls.state != ca_active) return; start = Sys_Milliseconds(); if (Cmd_Argc() == 2) { /* run without page flipping */ re.BeginFrame(0); for (i = 0; i < 128; i++) { cl.refdef.viewangles[1] = i / 128.0 * 360.0; re.RenderFrame(&cl.refdef); } re.EndFrame(); } else { for (i = 0; i < 128; i++) { cl.refdef.viewangles[1] = i / 128.0 * 360.0; re.BeginFrame(0); re.RenderFrame(&cl.refdef); re.EndFrame(); } } stop = Sys_Milliseconds(); time = (stop - start) / 1000.0; Com_Printf("%f seconds (%f fps)\n", time, 128 / time); } /* * ================= SCR_AddDirtyPoint ================= */ void SCR_AddDirtyPoint(int x, int y) { if (x < scr_dirty.x1) scr_dirty.x1 = x; if (x > scr_dirty.x2) scr_dirty.x2 = x; if (y < scr_dirty.y1) scr_dirty.y1 = y; if (y > scr_dirty.y2) scr_dirty.y2 = y; } void SCR_DirtyScreen(void) { SCR_AddDirtyPoint(0, 0); SCR_AddDirtyPoint(viddef.width - 1, viddef.height - 1); } /* * ============== SCR_TileClear * * Clear any parts of the tiled background that were drawn on last frame * ============== */ void SCR_TileClear(void) { int i; int top , bottom, left, right; dirty_t clear; if (scr_drawall->value) SCR_DirtyScreen(); /* for power vr or broken page flippers... */ if (scr_con_current == 1.0) return; /* full screen console */ if (scr_viewsize->value == 100) return; /* full screen rendering */ if (cl.cinematictime > 0) return; /* full screen cinematic */ /* erase rect will be the union of the past three frames */ /* so tripple buffering works properly */ clear = scr_dirty; for (i = 0; i < 2; i++) { if (scr_old_dirty[i].x1 < clear.x1) clear.x1 = scr_old_dirty[i].x1; if (scr_old_dirty[i].x2 > clear.x2) clear.x2 = scr_old_dirty[i].x2; if (scr_old_dirty[i].y1 < clear.y1) clear.y1 = scr_old_dirty[i].y1; if (scr_old_dirty[i].y2 > clear.y2) clear.y2 = scr_old_dirty[i].y2; } scr_old_dirty[1] = scr_old_dirty[0]; scr_old_dirty[0] = scr_dirty; scr_dirty.x1 = 9999; scr_dirty.x2 = -9999; scr_dirty.y1 = 9999; scr_dirty.y2 = -9999; /* don't bother with anything convered by the console) */ top = scr_con_current * viddef.height; if (top >= clear.y1) clear.y1 = top; if (clear.y2 <= clear.y1) return; /* nothing disturbed */ top = scr_vrect.y; bottom = top + scr_vrect.height - 1; left = scr_vrect.x; right = left + scr_vrect.width - 1; if (clear.y1 < top) { /* clear above view screen */ i = clear.y2 < top - 1 ? clear.y2 : top - 1; re.DrawTileClear(clear.x1, clear.y1, clear.x2 - clear.x1 + 1, i - clear.y1 + 1, "backtile"); clear.y1 = top; } if (clear.y2 > bottom) {/* clear below view screen */ i = clear.y1 > bottom + 1 ? clear.y1 : bottom + 1; re.DrawTileClear(clear.x1, i, clear.x2 - clear.x1 + 1, clear.y2 - i + 1, "backtile"); clear.y2 = bottom; } if (clear.x1 < left) { /* clear left of view screen */ i = clear.x2 < left - 1 ? clear.x2 : left - 1; re.DrawTileClear(clear.x1, clear.y1, i - clear.x1 + 1, clear.y2 - clear.y1 + 1, "backtile"); clear.x1 = left; } if (clear.x2 > right) { /* clear left of view screen */ i = clear.x1 > right + 1 ? clear.x1 : right + 1; re.DrawTileClear(i, clear.y1, clear.x2 - i + 1, clear.y2 - clear.y1 + 1, "backtile"); clear.x2 = right; } } /* =============================================================== */ #define STAT_MINUS 10 /* num frame for '-' stats digit */ char *sb_nums[2][11] = { {"num_0", "num_1", "num_2", "num_3", "num_4", "num_5", "num_6", "num_7", "num_8", "num_9", "num_minus"}, {"anum_0", "anum_1", "anum_2", "anum_3", "anum_4", "anum_5", "anum_6", "anum_7", "anum_8", "anum_9", "anum_minus"} }; #define ICON_WIDTH 24 #define ICON_HEIGHT 24 #define CHAR_WIDTH 16 #define ICON_SPACE 8 /* * ================ SizeHUDString * * Allow embedded \n in the string ================ */ void SizeHUDString(char *string, int *w, int *h) { int lines, width, current; lines = 1; width = 0; current = 0; while (*string) { if (*string == '\n') { lines++; current = 0; } else { current++; if (current > width) width = current; } string++; } *w = width * 8; *h = lines * 8; } void DrawHUDString(char *string, int x, int y, int centerwidth, int xor) { int margin; char line[1024]; int width; int i; margin = x; while (*string) { /* scan out one line of text from the string */ width = 0; while (*string && *string != '\n') line[width++] = *string++; line[width] = 0; if (centerwidth) x = margin + (centerwidth - width * 8) / 2; else x = margin; for (i = 0; i < width; i++) { re.DrawChar(x, y, line[i] ^ xor, 256); x += 8; } if (*string) { string++; /* skip the \n */ x = margin; y += 8; } } } /* * ============== SCR_DrawField ============== */ void SCR_DrawField(int x, int y, int color, int width, int value) { char num[16], *ptr; int l; int frame; if (width < 1) return; /* draw number string */ if (width > 5) width = 5; SCR_AddDirtyPoint(x, y); SCR_AddDirtyPoint(x + width * CHAR_WIDTH + 2, y + 23); Com_sprintf(num, sizeof(num), "%i", value); l = strlen(num); if (l > width) l = width; x += 2 + CHAR_WIDTH * (width - l); ptr = num; while (*ptr && l) { if (*ptr == '-') frame = STAT_MINUS; else frame = *ptr - '0'; re.DrawScaledPic(x, y, 1, cl_hudalpha->value, sb_nums[color][frame], cl_hud_red->value, cl_hud_green->value, cl_hud_blue->value, false, true); x += CHAR_WIDTH; ptr++; l--; } } /* * =============== SCR_TouchPics * * Allows rendering code to cache all needed sbar graphics =============== */ void SCR_TouchPics(void) { int i, j; for (i = 0; i < 2; i++) for (j = 0; j < 11; j++) re.RegisterPic(sb_nums[i][j]); if (crosshair->value) { if (crosshair->value > 20) crosshair->value = 20; Com_sprintf(crosshair_pic, sizeof(crosshair_pic), "ch%i", (int)(crosshair->value)); re.DrawGetPicSize(&crosshair_width, &crosshair_height, crosshair_pic); if (!crosshair_width) crosshair_pic[0] = 0; } } /* * ================ SCR_ExecuteLayoutString * * ================ */ void SCR_ExecuteLayoutString(char *s) { int x, y; int value; char *token; int width; int index; clientinfo_t *ci; if (cls.state != ca_active || !cl.refresh_prepped) return; if (!s[0]) return; x = 0; y = 0; width = 3; while (s) { token = COM_Parse(&s); if (!strcmp(token, "xl")) { token = COM_Parse(&s); x = atoi(token); continue; } if (!strcmp(token, "xr")) { token = COM_Parse(&s); x = viddef.width + atoi(token); continue; } if (!strcmp(token, "xv")) { token = COM_Parse(&s); x = viddef.width / 2 - 160 + atoi(token); continue; } if (!strcmp(token, "yt")) { token = COM_Parse(&s); y = atoi(token); continue; } if (!strcmp(token, "yb")) { token = COM_Parse(&s); y = viddef.height + atoi(token); continue; } if (!strcmp(token, "yv")) { token = COM_Parse(&s); y = viddef.height / 2 - 120 + atoi(token); continue; } if (!strcmp(token, "pic")) { /* draw a pic from a stat number */ token = COM_Parse(&s); value = cl.frame.playerstate.stats[atoi(token)]; if (value >= MAX_IMAGES) Com_Error(ERR_DROP, "Pic >= MAX_IMAGES"); if (cl.configstrings[CS_IMAGES + value]) { SCR_AddDirtyPoint(x, y); SCR_AddDirtyPoint(x + 23, y + 23); re.DrawScaledPic(x, y, 1, cl_hudalpha->value, cl.configstrings[CS_IMAGES + value], 1, 1, 1, false, true); } continue; } if (!strcmp(token, "client")) { /* draw a deathmatch client block */ int score, ping, time; token = COM_Parse(&s); x = viddef.width / 2 - 160 + atoi(token); token = COM_Parse(&s); y = viddef.height / 2 - 120 + atoi(token); SCR_AddDirtyPoint(x, y); SCR_AddDirtyPoint(x + 159, y + 31); token = COM_Parse(&s); value = atoi(token); if (value >= MAX_CLIENTS || value < 0) Com_Error(ERR_DROP, "client >= MAX_CLIENTS"); ci = &cl.clientinfo[value]; token = COM_Parse(&s); score = atoi(token); token = COM_Parse(&s); ping = atoi(token); token = COM_Parse(&s); time = atoi(token); DrawAltString(x + 32, y, ci->name); DrawString(x + 32, y + 8, "Score: "); DrawAltString(x + 32 + 7 * 8, y + 8, va("%i", score)); DrawString(x + 32, y + 16, va("Ping: %i", ping)); DrawString(x + 32, y + 24, va("Time: %i", time)); if (!ci->icon) ci = &cl.baseclientinfo; re.DrawScaledPic(x, y, 1, cl_hudalpha->value, ci->iconname, cl_hud_red->value, cl_hud_green->value, cl_hud_blue->value, false, true); continue; } if (!strcmp(token, "ctf")) { /* draw a ctf client block */ int score, ping; char block[80]; token = COM_Parse(&s); x = viddef.width / 2 - 160 + atoi(token); token = COM_Parse(&s); y = viddef.height / 2 - 120 + atoi(token); SCR_AddDirtyPoint(x, y); SCR_AddDirtyPoint(x + 159, y + 31); token = COM_Parse(&s); value = atoi(token); if (value >= MAX_CLIENTS || value < 0) Com_Error(ERR_DROP, "client >= MAX_CLIENTS"); ci = &cl.clientinfo[value]; token = COM_Parse(&s); score = atoi(token); token = COM_Parse(&s); ping = atoi(token); if (ping > 999) ping = 999; Com_sprintf(block, sizeof(block), "%3d %3d %-12.12s", score, ping, ci->name); if (value == cl.playernum) DrawAltString(x, y, block); else DrawString(x, y, block); continue; } if (!strcmp(token, "picn")) { /* draw a pic from a name */ token = COM_Parse(&s); SCR_AddDirtyPoint(x, y); SCR_AddDirtyPoint(x + 23, y + 23); re.DrawScaledPic(x, y, 1, cl_hudalpha->value, token, 1, 1, 1, false, true); continue; } if (!strcmp(token, "num")) { /* draw a number */ token = COM_Parse(&s); width = atoi(token); token = COM_Parse(&s); value = cl.frame.playerstate.stats[atoi(token)]; SCR_DrawField(x, y, 0, width, value); continue; } if (!strcmp(token, "hnum")) { /* health number */ int color; width = 3; value = cl.frame.playerstate.stats[STAT_HEALTH]; if (value > 25) color = 0; /* green */ else if (value > 0) color = (cl.frame.serverframe >> 2) & 1; /* flash */ else color = 1; if (cl.frame.playerstate.stats[STAT_FLASHES] & 1) re.DrawScaledPic(x, y, 1, cl_hudalpha->value, "field_3", cl_hud_red->value, cl_hud_green->value, cl_hud_blue->value, false, true); SCR_DrawField(x, y, color, width, value); continue; } if (!strcmp(token, "anum")) { /* ammo number */ int color; width = 3; value = cl.frame.playerstate.stats[STAT_AMMO]; if (value > 5) color = 0; /* green */ else if (value >= 0) color = (cl.frame.serverframe >> 2) & 1; /* flash */ else continue; /* negative number = don't * show */ if (cl.frame.playerstate.stats[STAT_FLASHES] & 4) re.DrawScaledPic(x, y, 1, cl_hudalpha->value, "field_3", cl_hud_red->value, cl_hud_green->value, cl_hud_blue->value, false, true); SCR_DrawField(x, y, color, width, value); continue; } if (!strcmp(token, "rnum")) { /* armor number */ int color; width = 3; value = cl.frame.playerstate.stats[STAT_ARMOR]; if (value < 1) continue; color = 0; /* green */ if (cl.frame.playerstate.stats[STAT_FLASHES] & 2) re.DrawScaledPic(x, y, 1, cl_hudalpha->value, "field_3", cl_hud_red->value, cl_hud_green->value, cl_hud_blue->value, false, true); SCR_DrawField(x, y, color, width, value); continue; } if (!strcmp(token, "stat_string")) { token = COM_Parse(&s); index = atoi(token); if (index < 0 || index >= MAX_CONFIGSTRINGS) Com_Error(ERR_DROP, "Bad stat_string index"); index = cl.frame.playerstate.stats[index]; if (index < 0 || index >= MAX_CONFIGSTRINGS) Com_Error(ERR_DROP, "Bad stat_string index"); DrawString(x, y, cl.configstrings[index]); continue; } if (!strcmp(token, "cstring")) { token = COM_Parse(&s); DrawHUDString(token, x, y, 320, 0); continue; } if (!strcmp(token, "string")) { token = COM_Parse(&s); DrawString(x, y, token); continue; } if (!strcmp(token, "cstring2")) { token = COM_Parse(&s); DrawHUDString(token, x, y, 320, 0x80); continue; } if (!strcmp(token, "string2")) { token = COM_Parse(&s); DrawAltString(x, y, token); continue; } if (!strcmp(token, "if")) { /* draw a number */ token = COM_Parse(&s); value = cl.frame.playerstate.stats[atoi(token)]; if (!value) { /* skip to endif */ while (s && strcmp(token, "endif")) { token = COM_Parse(&s); } } continue; } } } /* * ================ SCR_DrawStats * * The status bar is a small layout program that is based on the stats array * ================ */ void SCR_DrawStats(void) { if (!cl_drawhud->value) return; SCR_ExecuteLayoutString(cl.configstrings[CS_STATUSBAR]); } /* * ================ SCR_DrawLayout * * ================ */ #define STAT_LAYOUTS 13 void SCR_DrawLayout(void) { if (!cl.frame.playerstate.stats[STAT_LAYOUTS]) return; SCR_ExecuteLayoutString(cl.layout); } /* ======================================================= */ /* * ================== SCR_UpdateScreen * * This is called every frame, and can also be called explicitly to flush text * to the screen. ================== */ struct tm *now; #define FPS_FRAMES 128 void SCR_UpdateScreen(void) { int numframes; int i; float separation[2] = {0, 0}; static int prevTime = 0, index = 0; static char fps [32]; time_t tnow; char timebuf[32]; char s[96]; int y = 0; int avg; tnow = time((time_t *) 0); now = localtime(&tnow); if (!dedicated->value) {/* FIXME don't know why reason crashes * dedicated servers. */ y = viddef.height * cl_draw_y->value; } /* * if the screen is disabled (loading plaque is up, or vid mode * changing) */ /* do nothing at all */ if (cls.disable_screen) { if (Sys_Milliseconds() - cls.disable_screen > 120000) { cls.disable_screen = 0; Com_Printf("Loading plaque timed out.\n"); } return; } if (!scr_initialized || !con.initialized) return; /* not initialized yet */ /* * * range check cl_camera_separation so we don't inadvertently fry * someone's * brain */ #ifdef REDBLUE if (cl_stereo_separation->value > 10.0) Cvar_SetValue("cl_stereo_separation", 10.0); #else if (cl_stereo_separation->value > 1.0) Cvar_SetValue("cl_stereo_separation", 1.0); #endif else if (cl_stereo_separation->value < 0) Cvar_SetValue("cl_stereo_separation", 0.0); #ifdef REDBLUE if (!cl_stereo->value) Cvar_SetValue("cl_stereo", 1); numframes = 2; separation[0] = -cl_stereo_separation->value / 2.0; separation[1] = cl_stereo_separation->value / 2.0; #else if (cl_stereo->value) { numframes = 2; separation[0] = -cl_stereo_separation->value / 2; separation[1] = cl_stereo_separation->value / 2; } else { separation[0] = 0; separation[1] = 0; numframes = 1; } #endif for (i = 0; i < numframes; i++) { re.BeginFrame(separation[i]); if (scr_draw_loading == 2) { /* loading plaque over black screen */ int w , h; re.CinematicSetPalette(NULL); scr_draw_loading = false; re.DrawGetPicSize(&w, &h, "loading"); re.DrawPic((viddef.width - w) / 2, (viddef.height - h) / 2, "loading", 1.0); /* re.EndFrame(); */ /* return; */ } /* if a cinematic is supposed to be running, handle menus */ /* and console specially */ else if (cl.cinematictime > 0) { if (cls.key_dest == key_menu) { if (cl.cinematicpalette_active) { re.CinematicSetPalette(NULL); cl.cinematicpalette_active = false; } M_Draw(); /* re.EndFrame(); */ /* return; */ } else if (cls.key_dest == key_console) { if (cl.cinematicpalette_active) { re.CinematicSetPalette(NULL); cl.cinematicpalette_active = false; } SCR_DrawConsole(); /* re.EndFrame(); */ /* return; */ } else { SCR_DrawCinematic(); /* re.EndFrame(); */ /* return; */ } } else { /* make sure the game palette is active */ if (cl.cinematicpalette_active) { re.CinematicSetPalette(NULL); cl.cinematicpalette_active = false; } /* do 3D refresh drawing, and then update the screen */ SCR_CalcVrect(); /* clear any dirty part of the background */ SCR_TileClear(); V_RenderView(separation[i]); if (scr_debuggraph->value || scr_timegraph->value || scr_netgraph->value) SCR_DrawDebugGraph(); SCR_DrawStats(); if (cl.frame.playerstate.stats[STAT_LAYOUTS] & 1) SCR_DrawLayout(); if (cl.frame.playerstate.stats[STAT_LAYOUTS] & 2) CL_DrawInventory(); SCR_DrawNet(); SCR_CheckDrawCenterString(); if (cl_drawchathud->value) { DrawAltString(viddef.width * cl_draw_x->value, y, ChatMessages.chathud[0]); y += 8; DrawAltString(viddef.width * cl_draw_x->value, y, ChatMessages.chathud[1]); y += 8; DrawAltString(viddef.width * cl_draw_x->value, y, ChatMessages.chathud[2]); y += 8; DrawAltString(viddef.width * cl_draw_x->value, y, ChatMessages.chathud[3]); y += 10; } if (cl_drawclock->value) { /* 12hr, else 24hr */ if (cl_drawclock->value == 1) strftime(timebuf, 32, "Time : %H:%M", now); else strftime(timebuf, 32, "Time : %I:%M %p", now); DrawString(viddef.width * cl_draw_x->value, y, timebuf); y += 9; } if (cl_drawdate->value) { strftime(timebuf, 32, "Date: %a, %d %b %Y", now); DrawString(viddef.width * cl_draw_x->value, y, timebuf); y += 9; } if (cl_drawfps->value) { index++; if ((index % FPS_FRAMES) == 0) { Com_sprintf(fps, 32, "FPS : %d", (int)(1000 / ((float)(Sys_Milliseconds() - prevTime) / FPS_FRAMES))); prevTime = Sys_Milliseconds(); } if (index > FPS_FRAMES) { DrawString(viddef.width * cl_draw_x->value, y, fps); y += 9; } } if (cl_drawmaptime->value) { char temp [32]; int time , hour, mins, secs; if (!cl_drawmaptime->value) return; if (cl_drawmaptime->value) time = cl.time / 1000; hour = time / 3600; mins = (time % 3600) / 60; secs = time % 60; if (hour > 0) Com_sprintf(temp, sizeof(temp), " Map time : %i:%02i:%02i", hour, mins, secs); else Com_sprintf(temp, sizeof(temp), "Map time : %i:%02i", mins, secs); DrawString(viddef.width * cl_draw_x->value, y, temp); y += 9; } if (cl_drawping->value) { avg = 0; for (i = 0; i < MAX_SCRN_PINGS; i++) { avg += ScrnPings[i]; } avg /= MAX_SCRN_PINGS; Com_sprintf(s, sizeof(s), "Ping : %dms", avg); DrawString(viddef.width * cl_draw_x->value, y, s); y += 9; } if (cl_drawrate->value) { float avgs = 0, avgr = 0; int time; time = Sys_Milliseconds(); for (i = 0; i < MAX_NET_HISTORY; i++) avgs += Net_History.SendsSize[i]; if (Net_History.SendsStartTime) avgs /= (time - Net_History.SendsStartTime); avgs *= 1000.0; avgs /= 1024.0; for (i = 0; i < MAX_NET_HISTORY; i++) avgr += Net_History.RecsSize[i]; if (Net_History.RecsStartTime) avgr /= (time - Net_History.RecsStartTime); avgr *= 1000.0; avgr /= 1024.0; Com_sprintf(s, sizeof(s), "DL:%2.1fKB/Sec UL:%2.1fKB/Sec", avgr, avgs); DrawString(viddef.width * cl_draw_x->value, y, s); y += 9; } if (scr_timegraph->value) SCR_DebugGraph(cls.frametime * 300, 0); SCR_DrawPause(); SCR_DrawConsole(); M_Draw(); SCR_DrawLoading(); } } re.EndFrame(); }