/* * Copyright(c) 1997-2001 Id Software, Inc. * Copyright(c) 2002 The Quakeforge Project. * Copyright(c) 2006 Quetoo. * * 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. */ #include "client.h" qboolean scr_initialized; // ready to draw vrect_t scr_vrect; // position of render window on screen cvar_t *scr_crosshair; cvar_t *scr_viewsize; cvar_t *scr_centertime; cvar_t *cl_netgraph; cvar_t *cl_timegraph; cvar_t *cl_debuggraph; cvar_t *cl_graphheight; cvar_t *cl_graphscale; cvar_t *cl_graphshift; char crosshair_pic[MAX_QPATH]; int crosshair_width, crosshair_height; extern cvar_t *cl_showfps; extern cvar_t *cl_showspeed; /* CL_AddNetgraph A new packet was just parsed */ void CL_AddNetgraph(void){ int i; int in; int ping; // we only need to do our accounting when asked to if(!cl_netgraph->value) return; // if using the debuggraph for something else, don't // add the net lines if(cl_debuggraph->value || cl_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]; ping /= 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 */ void SCR_DrawDebugGraph(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; for(a = 0; a < w; a++){ i = (current - 1 - a + 1024) & 1023; v = values[i].value; color = values[i].color; v = v * cl_graphscale->value + cl_graphshift->value; if(v < 0) v += cl_graphheight->value * (1 +(int)(-v / cl_graphheight->value)); h = (int)v % (int)cl_graphheight->value; Draw_Fill(x + w - 1 - a, y - h, 1, h, color); } } char scr_centerstring[MAX_STRING_CHARS]; float scr_centertime_start; // for slow victory printing float scr_centertime_off; 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; 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; s = str; while(*s){ if(*s == '\n') scr_center_lines++; s++; } 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); Con_ClearNotify(); } void SCR_DrawCenterString(void){ char *start; int l; int j; int x, y; int remaining; // 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; for(j = 0; j < l; j++, x += 8){ Draw_Char(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_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("scr_viewsize", "40"); if(scr_viewsize->value > 100) Cvar_Set("scr_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("scr_viewsize", scr_viewsize->value + 10); } /* SCR_SizeDown_f Keybinding command */ void SCR_SizeDown_f(void){ Cvar_SetValue("scr_viewsize", scr_viewsize->value - 10); } /* SCR_Sky_f Set a specific skybox image set */ void SCR_Sky_f(void){ if(Cmd_Argc() != 2){ Com_Printf("Usage: sky \n"); return; } GL_SetSky(Cmd_Argv(1)); } /* SCR_Init */ void SCR_Init(void){ scr_crosshair = Cvar_Get("scr_crosshair", "1", CVAR_ARCHIVE); scr_viewsize = Cvar_Get("scr_viewsize", "100", CVAR_ARCHIVE); scr_centertime = Cvar_Get("scr_centertime", "2.5", 0); cl_netgraph = Cvar_Get("cl_netgraph", "0", 0); cl_timegraph = Cvar_Get("cl_timegraph", "0", 0); cl_debuggraph = Cvar_Get("cl_debuggraph", "0", 0); cl_graphheight = Cvar_Get("cl_graphheight", "24", 0); cl_graphscale = Cvar_Get("cl_graphscale", "1", 0); cl_graphshift = Cvar_Get("cl_graphshift", "0", 0); Cmd_AddCommand("sizeup", SCR_SizeUp_f); Cmd_AddCommand("sizedown", SCR_SizeDown_f); Cmd_AddCommand("sky", SCR_Sky_f); SCR_CalcVrect(); scr_initialized = true; } /* 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); return; } if(cls.key_dest == key_console || cls.state != ca_active || !cl.view_prepped){ // connected, but can't render Con_DrawConsole(0.5); return; } if(cls.key_dest == key_game || cls.key_dest == key_message) Con_DrawNotify(); // only draw notify in game } #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[MAX_STRING_CHARS]; 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++){ Draw_Char(x, y, line[i] ^ xor); 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; 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'; Draw_Pic(x, y, sb_nums[color][frame]); 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++) Draw_FindPic(sb_nums[i][j]); if(scr_crosshair->value){ if(scr_crosshair->value < 0) scr_crosshair->value = 1; Com_sprintf(crosshair_pic, sizeof(crosshair_pic), "ch%i", (int)(scr_crosshair->value)); Draw_GetPicSize(&crosshair_width, &crosshair_height, crosshair_pic); if(crosshair_width < 1) // dont bother 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.view_prepped || !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]){ Draw_Pic(x, y, cl.configstrings[CS_IMAGES + value]); } 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); 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; Draw_Pic(x, y, ci->iconname); 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); 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; sprintf(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); Draw_Pic(x, y, token); 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) Draw_Pic(x, y, "field_3"); 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) Draw_Pic(x, y, "field_3"); 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) Draw_Pic(x, y, "field_3"); 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_DrawCrosshair */ void SCR_DrawCrosshair(void){ if(cls.state != ca_active) return; if(!scr_crosshair->value) return; if(scr_crosshair->modified){ scr_crosshair->modified = false; SCR_TouchPics(); } if(!crosshair_pic[0]) return; Draw_Pic(scr_vrect.x + ((scr_vrect.width - crosshair_width) >> 1), scr_vrect.y + ((scr_vrect.height - crosshair_height) >> 1), crosshair_pic); } /* SCR_DrawStats The status bar is a small layout program based on the stats array */ void SCR_DrawStats(void){ 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); } int millis = 0, frames_this_second = 0, packets_this_second = 0; static char pps[8], fps[8], speed[8]; /* SCR_UpdateScreen This is called every frame, and can also be called explicitly to flush text to the screen. */ void SCR_UpdateScreen(void){ extern qboolean update_viewdef; if(!scr_initialized || !con.initialized) return; // not initialized yet GL_BeginFrame(); if(cls.state != ca_active || scr_viewsize->value < 100) // fill screen Draw_Fill(0, 0, viddef.width, viddef.height, 0); if(scr_viewsize->modified || update_viewdef){ // update vrect scr_viewsize->modified = false; SCR_CalcVrect(); } V_UpdateView(); // update viewdef GL_RenderFrame(&cl.viewdef); // draw 3d GL_SetGL2D(); // prepare for 2d if(cl_debuggraph->value || cl_timegraph->value || cl_netgraph->value){ SCR_DebugGraph(cls.frametime * 300, 0); SCR_DrawDebugGraph(); } SCR_DrawCrosshair(); SCR_DrawStats(); if(cl.frame.playerstate.stats[STAT_LAYOUTS] & 1) SCR_DrawLayout(); if(cl.frame.playerstate.stats[STAT_LAYOUTS] & 2) CL_DrawInventory(); SCR_CheckDrawCenterString(); if(cl_showfps->value && cls.state == ca_active){ if(curtime - millis > 1000){ // reset counters and draw it snprintf(fps, sizeof(fps), "%4dfps", frames_this_second); snprintf(pps, sizeof(pps), "%4dpps", packets_this_second); millis = curtime; frames_this_second = 0; packets_this_second = 0; } frames_this_second++; // packets per second is incremented elsewhere DrawString(viddef.width - 56, viddef.height - 24, fps); DrawString(viddef.width - 56, viddef.height - 16, pps); } if(cl_showspeed->value && cls.state == ca_active){ player_state_t *ps = &cl.frame.playerstate; vec3_t hv; VectorSet(hv, ps->pmove.velocity[0] * 0.125, ps->pmove.velocity[1] * 0.125, 0); snprintf(speed, sizeof(speed), "%4.0fspd", VectorLength(hv)); DrawString(viddef.width - 56, viddef.height - 8, speed); } SCR_DrawConsole(); GL_EndFrame(); }