/* 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. */ // cg_screen.c -- master status bar, crosshairs, hud, 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 "cg_local.h" vrect_t scr_vrect; cvar_t *cg_viewSize; cvar_t *cg_centerTime; cvar_t *cg_showFPS; cvar_t *cg_showPointedPlayer; cvar_t *cg_showHUD; cvar_t *cg_draw2D; cvar_t *cg_weaponlist; cvar_t *cg_debugLoading; cvar_t *cg_crosshair; cvar_t *cg_crosshair_size; cvar_t *cg_crosshair_color; cvar_t *cg_crosshair_strong; cvar_t *cg_crosshair_strong_size; cvar_t *cg_crosshair_strong_color; cvar_t *cg_clientHUD; cvar_t *cg_debug_HUD; cvar_t *cg_showSpeedMeter; cvar_t *cg_showTimer; cvar_t *cg_showPlayerNames; cvar_t *cg_showPlayerNames_alpha; cvar_t *cg_showPlayerNames_zfar; cvar_t *cg_showPlayerNames_xoffset; cvar_t *cg_showPlayerNames_yoffset; cvar_t *cg_scoreboardFont; cvar_t *cg_scoreboardWidthScale; cvar_t *cg_showWeaponSelect; // wsw : pb : display a little box around selected weapon in weaponlist cvar_t *cg_showTeamLocations; /* =============================================================================== 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_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; Q_strncpyz( scr_centerstring, str, sizeof(scr_centerstring) ); scr_centertime_off = cg_centerTime->value; scr_centertime_start = cg.time; // count the number of lines for centering scr_center_lines = 1; s = scr_centerstring; while( *s ) if( *s++ == '\n' ) scr_center_lines++; } void SCR_CenterPrintToUpper( char *str ) { char *s; Q_strncpyz( scr_centerstring, str, sizeof(scr_centerstring) ); scr_centertime_off = cg_centerTime->value; scr_centertime_start = cg.time; // count the number of lines for centering scr_center_lines = 1; s = scr_centerstring; while( *s ) { if( *s == '\n' ) { scr_center_lines++; } else { *s = toupper(*s); } s++; } } void SCR_DrawCenterString( void ) { int y; struct mufont_s *font = cgs.fontSystemMedium; char *helpmessage = scr_centerstring; int x = cgs.vidWidth / 2; int width = cgs.vidWidth / 2; size_t len; // don't draw when scoreboard is up if( cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_SCOREBOARD ) return; if( scr_center_lines <= 4 ) y = cgs.vidHeight*0.35; else y = 48; if( width < 320 ) width = 320; while( (len = trap_SCR_StrlenForWidth( helpmessage, font, width )) ) { trap_SCR_DrawStringLen( x, y, ALIGN_CENTER_TOP, helpmessage, len, font, colorWhite ); helpmessage += len; if( helpmessage[0] == '\n' ) { y += trap_SCR_strHeight( font ); helpmessage++; } } } void SCR_CheckDrawCenterString( void ) { scr_centertime_off -= cg.frameTime; if( scr_centertime_off <= 0 ) return; SCR_DrawCenterString(); } //============================================================================= /* ================= SCR_CalcVrect Sets scr_vrect, the coordinates of the rendered window ================= */ void SCR_CalcVrect( void ) { int size; // bound viewsize if( cg_viewSize->integer < 40 ) trap_Cvar_Set( "cg_viewsize", "40" ); else if( cg_viewSize->integer > 100 ) trap_Cvar_Set( "cg_viewsize", "100" ); size = cg_viewSize->integer; scr_vrect.width = cgs.vidWidth*size/100; scr_vrect.width &= ~7; scr_vrect.height = cgs.vidHeight*size/100; scr_vrect.height &= ~1; scr_vrect.x = (cgs.vidWidth - scr_vrect.width)/2; scr_vrect.y = (cgs.vidHeight - scr_vrect.height)/2; } /* ================= SCR_SizeUp_f Keybinding command ================= */ void SCR_SizeUp_f( void ) { trap_Cvar_SetValue( "cg_viewSize", cg_viewSize->integer + 10 ); } /* ================= SCR_SizeDown_f Keybinding command ================= */ void SCR_SizeDown_f( void ) { trap_Cvar_SetValue( "cg_viewSize", cg_viewSize->integer - 10 ); } //============================================================================ /* ================== SCR_Init ================== */ void SCR_Init (void) { cg_viewSize = trap_Cvar_Get( "cg_viewSize", "100", CVAR_ARCHIVE ); cg_showFPS = trap_Cvar_Get( "cg_showFPS", "0", CVAR_ARCHIVE ); cg_showHUD = trap_Cvar_Get( "cg_showHUD", "1", CVAR_ARCHIVE ); cg_draw2D = trap_Cvar_Get( "cg_draw2D", "1", 0 ); cg_centerTime = trap_Cvar_Get( "cg_centerTime", "2.5", 0 ); cg_debugLoading = trap_Cvar_Get( "cg_debugLoading", "0", CVAR_ARCHIVE ); cg_weaponlist = trap_Cvar_Get( "cg_weaponlist", "1", CVAR_ARCHIVE ); cg_crosshair = trap_Cvar_Get( "cg_crosshair", "1", CVAR_ARCHIVE ); cg_crosshair_size = trap_Cvar_Get( "cg_crosshair_size", "32", CVAR_ARCHIVE ); cg_crosshair_color = trap_Cvar_Get( "cg_crosshair_color", "255 255 255", CVAR_ARCHIVE ); cg_crosshair_color->modified = qtrue; cg_crosshair_strong = trap_Cvar_Get( "cg_crosshair_strong", "0", CVAR_ARCHIVE ); cg_crosshair_strong_size = trap_Cvar_Get( "cg_crosshair_strong_size", "32", CVAR_ARCHIVE ); cg_crosshair_strong_color = trap_Cvar_Get( "cg_crosshair_strong_color", "255 255 255", CVAR_ARCHIVE ); cg_crosshair_strong_color->modified = qtrue; // wsw : jal cg_clientHUD = trap_Cvar_Get( "cg_clientHUD", "default", CVAR_ARCHIVE ); cg_showTimer = trap_Cvar_Get( "cg_showTimer", "1", CVAR_ARCHIVE ); cg_showSpeedMeter = trap_Cvar_Get( "cg_showSpeedMeter", "0", CVAR_ARCHIVE ); cg_showPointedPlayer = trap_Cvar_Get( "cg_showPointedPlayer", "1", CVAR_ARCHIVE ); cg_showWeaponSelect = trap_Cvar_Get( "cg_showWeaponSelect", "1", CVAR_ARCHIVE ); // wsw : pb : display a little box around selected weapon in weaponlist cg_showTeamLocations = trap_Cvar_Get( "cg_showTeamLocations", "1", CVAR_ARCHIVE ); cg_showPlayerNames = trap_Cvar_Get( "cg_showPlayerNames", "1", CVAR_ARCHIVE ); cg_showPlayerNames_alpha = trap_Cvar_Get( "cg_showPlayerNames_alpha", "0.4", CVAR_ARCHIVE ); cg_showPlayerNames_zfar = trap_Cvar_Get( "cg_showPlayerNames_zfar", "824", CVAR_ARCHIVE ); cg_showPlayerNames_xoffset = trap_Cvar_Get( "cg_showPlayerNames_xoffset", "0", CVAR_ARCHIVE ); cg_showPlayerNames_yoffset = trap_Cvar_Get( "cg_showPlayerNames_yoffset", "16", CVAR_ARCHIVE ); cg_scoreboardFont = trap_Cvar_Get( "cg_scoreboardFont", DEFAULT_FONT_SCOREBOARD, CVAR_ARCHIVE ); cg_scoreboardWidthScale = trap_Cvar_Get( "cg_scoreboardWidthScale", "1.0", CVAR_ARCHIVE ); // wsw : hud debug prints cg_debug_HUD = trap_Cvar_Get( "cg_debug_HUD", "0", 0 ); // // register our commands // trap_Cmd_AddCommand( "sizeup", SCR_SizeUp_f ); trap_Cmd_AddCommand( "sizedown", SCR_SizeDown_f ); trap_Cmd_AddCommand( "help_hud", Cmd_CG_PrintHudHelp_f ); } /* ================== SCR_Shutdown ================== */ void SCR_Shutdown (void) { trap_Cmd_RemoveCommand( "sizeup" ); trap_Cmd_RemoveCommand( "sizedown" ); trap_Cmd_RemoveCommand( "help_hud" ); } /* ================ SCR_ParseValue ================ */ int SCR_ParseValue( char **s ) { int index; char *token; token = COM_Parse( s ); if( !token[0] ) return 0; else if( token[0] != '%' ) return atoi( token ); index = atoi( token + 1 ); if( index < 0 || index >= PS_MAX_STATS ) CG_Error( "Bad stat index: %i", index ); return cg.frame.playerState.stats[index]; } /* ============== SCR_DrawQuadEffect - wsw : jal ============== */ void SCR_DrawQuadEffect( void ) { vec4_t color; player_state_t *ps = &cg.frame.playerState; // effects are not updated for spectator entities if( ( ps->pmove.pm_type == PM_SPECTATOR && cg.chasedNum == cgs.playerNum ) || !( cg_entities[cg.chasedNum+1].current.effects & EF_QUAD ) ) return; //if( GS_Gametype_IsTeamBased( cg.frame.playerState.stats[STAT_GAMETYPE] ) ) { color[0] = 1.0f; color[1] = 0.1f; color[2] = 0.1f; color[3] = 0.3f; trap_R_DrawStretchPic( 0, 0, cgs.vidWidth, cgs.vidHeight, 0, 0, 1, 1, color, cgs.shaderWhite ); return; } } //============== //SCR_DrawPlayerNames // shows player names at their feets. // TODO: Healthbars on teammates. //============== void SCR_DrawPlayerNames( void ) { static vec4_t whiteTransparent = { 1.0f, 1.0f, 1.0f, 0.5f }; int i; centity_t *cent; vec2_t coords; vec3_t dir; float dist; trace_t trace; float fadeFrac; float alpha; if( !cg_showPlayerNames->integer || cg_showPlayerNames_zfar->value <= 0.0f ) return; // don't draw when scoreboard is up if( cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_SCOREBOARD ) return; alpha = cg_showPlayerNames_alpha->value; if( alpha <= 0.0f ) return; if( alpha > 1.0f ) alpha = 1.0f; for( i = 0; i < MAX_CLIENTS; i++ ) { if( i == cgs.playerNum || i == cg.chasedNum ) continue; //don't draw the name if the name is being drawn as pointed player if( cg_showPointedPlayer->integer && cg.pointedNum && cg.pointedNum == i+1 ) continue; cent = cg_entities + i + 1; if( cent->serverFrame != cg.frame.serverFrame ) continue; if( !cent->current.modelindex ) continue; // Kill if behind the view VectorSubtract( cent->ent.origin, cg.refdef.vieworg, dir ); dist = VectorNormalize2( dir, dir ) * cg.view_fracDistFOV; if( dist > cg_showPlayerNames_zfar->value ) continue; if( DotProduct( dir, cg.v_forward ) < 0 ) continue; fadeFrac = (cg_showPlayerNames_zfar->value - dist) / (cg_showPlayerNames_zfar->value * 0.25f); if( fadeFrac > 1.0f ) fadeFrac = 1.0f; whiteTransparent[3] = alpha * fadeFrac; CG_Trace( &trace, cg.refdef.vieworg, vec3_origin, vec3_origin, cent->ent.origin, cg.chasedNum+1, MASK_OPAQUE ); if( trace.fraction == 1.0f ) { // find the 3d point in 2d screen trap_R_TransformVectorToScreen( &cg.refdef, cent->ent.origin, coords ); trap_SCR_DrawStringLen( coords[0]+cg_showPlayerNames_xoffset->integer, (cg.refdef.height - coords[1])+cg_showPlayerNames_yoffset->integer, ALIGN_LEFT_MIDDLE, cgs.clientInfo[i].name, 12, cgs.fontSystemSmall, whiteTransparent ); } } } /* ============== SCR_DrawNet ============== */ void SCR_DrawNet( int x, int y, int w, int h, int align, vec4_t color ) { int incomingAcknowledged, outgoingSequence; trap_NET_GetCurrentState( &incomingAcknowledged, &outgoingSequence ); if( outgoingSequence - incomingAcknowledged < CMD_BACKUP-1 ) return; x = CG_HorizontalAlignForWidth( x, align, w ); y = CG_VerticalAlignForHeight( y, align, h ); trap_R_DrawStretchPic( x, y, w, h, 0, 0, 1, 1, color, CG_MediaShader(cgs.media.shaderNet) ); } /* ================= SCR_DrawCrosshair ================= */ void SCR_DrawCrosshair( int x, int y, int align ) { static vec4_t chColor = { 255, 255, 255, 255 }; static vec4_t chColorStrong = { 255, 255, 255, 255 }; int rgbcolor; int selected_weapon; if( cg_crosshair->modified ) { if( cg_crosshair->integer >= NUM_CROSSHAIRS || cg_crosshair->integer < 0 ) trap_Cvar_Set( "cg_crosshair", va("%i", 0 ) ); cg_crosshair->modified = qfalse; } if( cg_crosshair_size->modified ) { if( cg_crosshair_size->integer < 0 || cg_crosshair_size->integer > 2000 ) trap_Cvar_Set( "cg_crosshair_size", va("%i", 32 ) ); cg_crosshair_size->modified = qfalse; } if( cg_crosshair_color->modified ) { rgbcolor = COM_ReadColorRGBString( cg_crosshair_color->string ); if( rgbcolor != -1 ) { Vector4Set( chColor, COLOR_R(rgbcolor), COLOR_G(rgbcolor), COLOR_B(rgbcolor), 255 ); } else { Vector4Set( chColor, 255, 255, 255, 255 ); } cg_crosshair_color->modified = qfalse; } if( cg_crosshair_strong->modified ) { if( cg_crosshair_strong->integer >= NUM_CROSSHAIRS || cg_crosshair_strong->integer < 0 ) trap_Cvar_Set( "cg_crosshair_strong", va("%i", 0 ) ); cg_crosshair_strong->modified = qfalse; } if( cg_crosshair_strong_size->modified ) { if( cg_crosshair_strong_size->integer < 0 || cg_crosshair_strong_size->integer > 2000 ) trap_Cvar_Set( "cg_crosshair_strong_size", va("%i", 32 ) ); cg_crosshair_strong_size->modified = qfalse; } if( cg_crosshair_strong_color->modified ) { rgbcolor = COM_ReadColorRGBString( cg_crosshair_strong_color->string ); if( rgbcolor != -1 ) { Vector4Set( chColorStrong, COLOR_R(rgbcolor), COLOR_G(rgbcolor), COLOR_B(rgbcolor), 255 ); } else { Vector4Set( chColorStrong, 255, 255, 255, 255 ); } cg_crosshair_strong_color->modified = qfalse; } if( cg.latched_weapon == WEAP_NONE ) { selected_weapon = cg.frame.playerState.stats[STAT_WEAPON_ITEM]; } else { selected_weapon = cg.latched_weapon; } if( selected_weapon < WEAP_GUNBLADE || selected_weapon >= WEAP_TOTAL ) selected_weapon = WEAP_GUNBLADE; if( cg.frame.playerState.weaponlist[selected_weapon-1][1] && cg_crosshair_strong->integer ) // strong { x = CG_HorizontalAlignForWidth( x, align, cg_crosshair_strong_size->integer ); y = CG_VerticalAlignForHeight( y, align, cg_crosshair_strong_size->integer ); trap_R_DrawStretchPic( x, y, cg_crosshair_strong_size->integer, cg_crosshair_strong_size->integer, 0, 0, 1, 1, chColorStrong, CG_MediaShader(cgs.media.shaderCrosshair[cg_crosshair_strong->integer]) ); } else // weak { if( !cg_crosshair->integer ) return; x = CG_HorizontalAlignForWidth( x, align, cg_crosshair_size->integer ); y = CG_VerticalAlignForHeight( y, align, cg_crosshair_size->integer ); trap_R_DrawStretchPic( x, y, cg_crosshair_size->integer, cg_crosshair_size->integer, 0, 0, 1, 1, chColor, CG_MediaShader(cgs.media.shaderCrosshair[cg_crosshair->integer]) ); } } //================ //CG_DrawSpeed //================ /*static*/ void CG_DrawSpeed( int x, int y, int align, struct mufont_s *font, vec4_t color ) { static double dieciseisavo = (1.0/16.0); player_state_t *ps; vec3_t hvel; if( !cg.frameTime || !cg_showSpeedMeter->integer ) return; // do a rough one. Just the distance between ops and ps // find the previous frame to interpolate from ps = &cg.frame.playerState; VectorSet( hvel, ps->pmove.velocity[0]*dieciseisavo, ps->pmove.velocity[1]*dieciseisavo, 0 ); trap_SCR_DrawString( x, y, align, va( "speed:%4i", (int)VectorLength( hvel ) ), font, color ); } //================ //CG_DrawFPS //================ /*static */void CG_DrawFPS( int x, int y, int align, struct mufont_s *font, vec4_t color ) { #define FPSSAMPLESCOUNT 32 #define FPSSAMPLESMASK (FPSSAMPLESCOUNT-1) static int fps; static double oldtime; static int oldframecount; static float frameTimes[FPSSAMPLESCOUNT]; static float avFrameTime; int i; double t; if( !cg.frameTime || !cg_showFPS->integer ) return; frameTimes[cg.frameCount & FPSSAMPLESMASK] = cg.frameTime; if ( cg_showFPS->integer != 2 ) { for( avFrameTime = 0.0f, i = 0; i < FPSSAMPLESCOUNT; i++ ) { avFrameTime += frameTimes[cg.frameCount & FPSSAMPLESMASK]; } avFrameTime /= FPSSAMPLESCOUNT; fps = (int)(1.0f/avFrameTime); } else { t = cg.realTime * 0.001f; if ( (t - oldtime) >= 0.25 ) { // updates 4 times a second fps = (cg.frameCount - oldframecount) / (t - oldtime) + 0.5; oldframecount = cg.frameCount; oldtime = t; } } trap_SCR_DrawString( x, y, align, va( "%3ifps", fps ), font, color ); } //================ //CG_DrawClock //================ /*static*/ void CG_DrawClock( int x, int y, int align, struct mufont_s *font, vec4_t color ) { if( !cg_showTimer->integer ) return; if( cg.frame.match.state < MATCH_STATE_WARMUP || cg.frame.match.state > MATCH_STATE_PLAYTIME ) return; if( cg_showTimer->integer == 2 ) { trap_SCR_DrawString( x, y, align, va( "%02i:%02i", cg.frame.match.clock_mins, cg.frame.match.clock_secs ), font, color ); } else { trap_SCR_DrawString( x, y, align, va( "%02i:%02i:%02d", cg.frame.match.clock_mins, cg.frame.match.clock_secs, cg.frame.match.clock_msecs ), font, color ); } } //================ //CG_DrawPointed //================ static unsigned int point_remove_time; static int pointed_health; static int pointed_armor; static int pointed_armor_type; /*static*/ void CG_DrawPointed( int x, int y, int align, struct mufont_s *font, vec4_t color ) { float alpha; vec2_t coords; static vec4_t alphacolor, alphagreen = { 0, 1, 0, 0 }, alphared = { 1, 0, 0, 0 }, alphayellow = { 1, 1, 0, 0 }, alphamagenta= {1, 0, 1, 1}; int barwidth = trap_SCR_strWidth("_",font, 0) * 8; // size of 8 characters int barheight = trap_SCR_strHeight(font) * 0.25; // quarter of a character height int barseparator = barheight * 0.333; //never draw pointed in 3rd person if( cg.thirdPerson || !cg_showPointedPlayer->integer ) return; // don't draw when scoreboard is up if( cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_SCOREBOARD ) return; if( cg.frame.playerState.stats[STAT_POINTED_PLAYER] ) { qboolean mega = qfalse; cg.pointedNum = cg.frame.playerState.stats[STAT_POINTED_PLAYER]; //pointed_health = cg.frame.playerState.stats[STAT_POINTED_TEAMPLAYER]; point_remove_time = cg.time + 150; pointed_health = 3.2 * (cg.frame.playerState.stats[STAT_POINTED_TEAMPLAYER] &0x1F); mega = cg.frame.playerState.stats[STAT_POINTED_TEAMPLAYER]&0x20; pointed_armor = 5 * (cg.frame.playerState.stats[STAT_POINTED_TEAMPLAYER]>>6 &0x3F); pointed_armor_type = (cg.frame.playerState.stats[STAT_POINTED_TEAMPLAYER]>>12 &0xF); if( mega ) { pointed_health += 100; if( pointed_health > 200 ) pointed_health = 200; } } if( point_remove_time <= cg.time ) { cg.pointedNum = 0; } if( !cg.pointedNum ) return; // if 1 show at player head, if 2 at hud defined position if( cg_showPointedPlayer->integer < 2 ) { centity_t *cent; vec3_t headorigin; cent = &cg_entities[cg.pointedNum]; if( cent->serverFrame != cg.frame.serverFrame ) return; VectorSet( headorigin, cent->ent.origin[0], cent->ent.origin[1], cent->ent.origin[2]+playerbox_stand_maxs[2]+16 ); // find the 3d point in 2d screen trap_R_TransformVectorToScreen( &cg.refdef, headorigin, coords ); x = coords[0]; y = cg.refdef.height - coords[1]; align = ALIGN_CENTER_BOTTOM; } alpha = (float)( point_remove_time - cg.time ) / 150.0f; if( alpha > 1.0f ) alpha = 1.0f; alphacolor[0] = color[0]; alphacolor[1] = color[1]; alphacolor[2] = color[2]; alphacolor[3] = color[3] * alpha; trap_SCR_DrawString( x, y - trap_SCR_strHeight(font), align, cgs.clientInfo[ cg.pointedNum - 1 ].name, font, alphacolor ); if( !pointed_health ) return; //we have to aling first, then draw as left top, cause we want the bar to grow from left to right x = CG_HorizontalAlignForWidth( x, align, barwidth ); y = CG_VerticalAlignForHeight( y, align, barheight); //assign hud alpha to colored bar alphagreen[3] = alphared[3] = alphayellow[3] = alphamagenta[3] = color[3] * alpha; //make the white background more transparent than the hud defined color alphacolor[3] *= 0.5f; //background CG_DrawHUDRect( x, y, ALIGN_LEFT_TOP, barwidth, barheight*3, 100, 100, alphacolor ); //health y += barseparator; if( pointed_health > 100 ) { alphagreen[3] = alphamagenta[3] = 1.0f; CG_DrawHUDRect( x, y, ALIGN_LEFT_TOP, barwidth, barheight, 100, 100, alphagreen ); CG_DrawHUDRect( x, y, ALIGN_LEFT_TOP, barwidth, barheight, pointed_health - 100, 100, alphamagenta ); alphagreen[3] = alphamagenta[3] = alphared[3]; } else { CG_DrawHUDRect( x, y, ALIGN_LEFT_TOP, barwidth, barheight, pointed_health, 100, alphagreen ); } if( pointed_armor ) { y += barseparator + barheight; if( pointed_armor_type == 3 ) // red CG_DrawHUDRect( x, y, ALIGN_LEFT_TOP, barwidth, barheight, pointed_armor, 150, alphared ); else if( pointed_armor_type == 2 ) // yellow CG_DrawHUDRect( x, y, ALIGN_LEFT_TOP, barwidth, barheight, pointed_armor, 150, alphayellow ); else // green CG_DrawHUDRect( x, y, ALIGN_LEFT_TOP, barwidth, barheight, pointed_armor, 150, alphagreen ); } } //================ //SCR_DrawTeamInfo //================ void SCR_DrawTeamInfo( int x, int y, int align, struct mufont_s *font, vec4_t color ) { char string[128]; int team; int teammate; char *ptr, *tok, *loc; int height, pixheight; int locationTag; #ifdef VSAYS centity_t *cent; #endif if( !(cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_TEAMTAB) ) return; // don't draw when scoreboard is up if( cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_SCOREBOARD ) return; if( !cg_showHUD->integer|| !cg_showTeamLocations->integer ) return; team = cg.frame.playerState.stats[STAT_TEAM]; if( team <= TEAM_PLAYERS || team >= GS_MAX_TEAMS || !GS_Gametype_IsTeamBased(cg.frame.playerState.stats[STAT_GAMETYPE]) || cg.frame.playerState.stats[STAT_GAMETYPE] == GAMETYPE_DUEL ) return; // time to parse the teaminfo string if( !strlen(cg.teaminfo) ) return; height = trap_SCR_strHeight( font ); // find longest line ptr = cg.teaminfo; pixheight = 0; while( ptr ) { tok = COM_Parse( &ptr ); if( !tok || !strlen(tok) ) break; teammate = atoi(tok); if( teammate < 0 || teammate >= MAX_CLIENTS ) break; loc = COM_Parse( &ptr ); if( !loc || !strlen(loc) ) break; locationTag = atoi(loc); if( locationTag >= MAX_LOCATIONS ) locationTag = 0; pixheight += height; } y = CG_VerticalAlignForHeight( y, align, pixheight ); ptr = cg.teaminfo; while( ptr ) { tok = COM_Parse( &ptr ); if( !tok || !strlen(tok) ) return; teammate = atoi(tok); if( teammate < 0 || teammate >= MAX_CLIENTS ) return; loc = COM_Parse( &ptr ); if( !loc || !strlen(loc) ) return; locationTag = atoi(loc); if( locationTag >= MAX_LOCATIONS ) locationTag = 0; Q_snprintfz( string, sizeof(string), "%s%s %s%s", cgs.clientInfo[teammate].name, S_COLOR_WHITE, cgs.configStrings[CS_LOCATIONS+locationTag], S_COLOR_WHITE ); // draw the head-icon in the case this player has one #ifdef VSAYS cent = &cg_entities[teammate+1]; if( cent->localEffects[LOCALEFFECT_VSAY_HEADICON_TIMEOUT] > cg.time && cent->localEffects[LOCALEFFECT_VSAY_HEADICON] > 0 && cent->localEffects[LOCALEFFECT_VSAY_HEADICON] < VSAY_TOTAL ) { trap_R_DrawStretchPic( CG_HorizontalAlignForWidth( x, align, height ), CG_VerticalAlignForHeight( y, align, height ), height, height, 0, 0, 1, 1, color, CG_MediaShader(cgs.media.shaderVSayIcon[cent->localEffects[LOCALEFFECT_VSAY_HEADICON]]) ); } #endif trap_SCR_DrawString( x + height * ( align % 3 == 0 ), y, align, string, font, color ); y += height; } } //============================================================================= //================ //CG_LoadLayout //================ void CG_LoadLayout( char *s ) { Q_strncpyz( cg.layout, s, sizeof(cg.layout) ); } //================ //CG_InGameMenu //================ void CG_InGameMenu( void ) { static char menuparms[MAX_STRING_CHARS]; int is_challenger = 0, needs_ready = 0; int gametype = cg.frame.playerState.stats[STAT_GAMETYPE]; int realteam; realteam = (cg.frame.playerState.pmove.pm_type == PM_CHASECAM) ? TEAM_SPECTATOR : cg.frame.playerState.stats[STAT_TEAM]; if( cgs.serverRules.has_challengers && realteam == TEAM_SPECTATOR ) is_challenger = (cg.frame.valid && (cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_CHALLENGER)); if( cg.frame.match.state <= MATCH_STATE_WARMUP && realteam != TEAM_SPECTATOR ) needs_ready = !(cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_READY); Q_snprintfz( menuparms, sizeof(menuparms), "menu_game %i %i %i %i %i \"Warsow %s\"", gametype , GS_Gametype_IsTeamBased(gametype), realteam, (realteam == TEAM_SPECTATOR)?(cgs.serverRules.has_challengers + is_challenger):0, needs_ready, GS_Gametype_ShortName(gametype) ); trap_Cmd_ExecuteText( EXEC_APPEND, menuparms ); } //============================================================================= //============== //SCR_DrawLoading //============== void SCR_DrawLoading( void ) { char str[MAX_QPATH]; struct mufont_s *font = cgs.fontSystemBig; if( cgs.configStrings[CS_MAPNAME][0] ) { // draw a level shot trap_R_DrawStretchPic( 0, 0, cgs.vidWidth, cgs.vidHeight, 0, 0, 1, 1, colorWhite, cgs.shaderLevelshot ); trap_R_DrawStretchPic( 0, 0, cgs.vidWidth, cgs.vidHeight, 0, 0, 2.5, 2, colorWhite, cgs.shaderLevelshotDetail ); // draw map name Q_snprintfz( str, sizeof(str), "Loading %s", cgs.configStrings[CS_MAPNAME] ); trap_SCR_DrawString( cgs.vidWidth/2, 16, ALIGN_CENTER_TOP, str, font, colorWhite ); // what we're loading at the moment if( cg.loadingstring[0] ) { if ( cg.loadingstring[0] == '-' && !cg.loadingstring[1]) Q_strncpyz( str, "awaiting snapshot...", sizeof(str) ); else Q_snprintfz( str, sizeof(str), "loading... %s", cg.loadingstring ); trap_SCR_DrawString( cgs.vidWidth/2, 96, ALIGN_CENTER_TOP, str, font, colorWhite ); } if( cg.checkname[0] ) { char prefix[] = "filename: "; Q_snprintfz( str, sizeof(str), "%s%s", prefix, cg.checkname ); trap_SCR_DrawString( cgs.vidWidth/2, cgs.vidHeight - 20, ALIGN_CENTER_TOP, str, font, colorWhite ); } } } //================ //CG_LoadingString //================ void CG_LoadingString( char *str ) { Q_strncpyz( cg.loadingstring, str, sizeof(cg.loadingstring) ); trap_R_UpdateScreen (); } //================ //CG_LoadingFilename //================ void CG_LoadingFilename( char *str ) { if ( !cg_debugLoading->integer ) { cg.checkname[0] = 0; return; } Q_strncpyz( cg.checkname, str, sizeof(cg.checkname) ); trap_R_UpdateScreen (); } //============== //SCR_TileClearRect // //This repeats tile graphic to fill the screen around a sized down //refresh window. //============== void SCR_TileClearRect( int x, int y, int w, int h, struct shader_s *shader ) { float iw, ih; iw = 1.0f / 64.0; ih = 1.0f / 64.0; trap_R_DrawStretchPic( x, y, w, h, x*iw, y*ih, (x+w)*iw, (y+h)*ih, colorWhite, shader ); } /* ============== SCR_TileClear Clear any parts of the tiled background that were drawn on last frame ============== */ void SCR_TileClear( void ) { int w, h; int top, bottom, left, right; struct shader_s *backTile; if ( cg_viewSize->integer == 100 ) return; // full screen rendering w = cgs.vidWidth; h = cgs.vidHeight; top = scr_vrect.y; bottom = top + scr_vrect.height-1; left = scr_vrect.x; right = left + scr_vrect.width-1; backTile = CG_MediaShader ( cgs.media.shaderBackTile ); // clear above view screen SCR_TileClearRect( 0, 0, w, top, backTile ); // clear below view screen SCR_TileClearRect( 0, bottom, w, h - bottom, backTile ); // clear left of view screen SCR_TileClearRect( 0, top, left, bottom - top + 1, backTile ); // clear left of view screen SCR_TileClearRect( right, top, w - right, bottom - top + 1, backTile ); } //=============================================================== /* ================ SCR_ExecuteLayoutString ================ */ void SCR_ExecuteLayoutString( char *s ) { int x, y, w, h; int align; int value; char *token; int width; int index, stack; qboolean skip; qboolean flash; vec4_t color, flashColor; if( !s || !s[0] ) return; x = y = 0; w = h = 1; align = ALIGN_LEFT_TOP; width = 3; Vector4Copy( colorWhite, color ); Vector4Copy( colorWhite, flashColor ); stack = 0; skip = qfalse; flash = qfalse; // wsw : jal : we aren't using flashes while( s ) { token = COM_Parse( &s ); if( stack > 0 ) { if( !Q_stricmp( token, "endif") ) { stack--; if( !stack ) skip = qfalse; continue; } } if( !Q_stricmp( token, "if" ) ) { if( skip || !SCR_ParseValue( &s ) ) { stack++; skip = qtrue; // skip to endif } } else if( !Q_stricmp( token, "ifeq" ) ) { if( skip || SCR_ParseValue( &s ) != SCR_ParseValue( &s ) ) { stack++; skip = qtrue; // skip to endif } } else if( !Q_stricmp( token, "ifbit" ) ) { if( skip || !(SCR_ParseValue( &s ) & SCR_ParseValue( &s )) ) { stack++; skip = qtrue; // skip to endif } } else if( !Q_stricmp( token, "ifle" ) ) { if( skip || SCR_ParseValue( &s ) > SCR_ParseValue( &s ) ) { stack++; skip = qtrue; // skip to endif } } else if( !Q_stricmp( token, "ifl" ) ) { if( skip || SCR_ParseValue( &s ) >= SCR_ParseValue( &s ) ) { stack++; skip = qtrue; // skip to endif } } else if( !Q_stricmp( token, "ifge" ) ) { if( skip || SCR_ParseValue( &s ) < SCR_ParseValue( &s ) ) { stack++; skip = qtrue; // skip to endif } } else if( !Q_stricmp( token, "ifg" ) ) { if( skip || SCR_ParseValue( &s ) <= SCR_ParseValue( &s ) ) { stack++; skip = qtrue; // skip to endif } } else if( !skip ) { if( !Q_stricmp( token, "xl" ) ) { x = (int)(SCR_ParseValue(&s)*cgs.vidWidth/800); } else if( !Q_stricmp( token, "xr" ) ) { x = cgs.vidWidth + (int)(SCR_ParseValue(&s)*cgs.vidWidth/800); } else if( !Q_stricmp( token, "xv" ) ) { x = cgs.vidWidth / 2 + (int)(SCR_ParseValue(&s)*cgs.vidWidth/800); } else if( !Q_stricmp( token, "yt" ) ) { y = (int)(SCR_ParseValue(&s)*cgs.vidHeight/600); } else if( !Q_stricmp( token, "yb" ) ) { y = cgs.vidHeight + (int)(SCR_ParseValue(&s)*cgs.vidHeight/600); } else if( !Q_stricmp( token, "yv" ) ) { y = cgs.vidHeight / 2 + (int)(SCR_ParseValue(&s)*cgs.vidHeight/600); } else if( !Q_stricmp( token, "align" ) ) { align = (SCR_ParseValue(&s)-1)+3*(SCR_ParseValue(&s)-1); } else if( !Q_stricmp( token, "size" ) ){ w = (int)(SCR_ParseValue( &s )*cgs.vidWidth/800); h = (int)(SCR_ParseValue( &s )*cgs.vidHeight/600); } else if( !Q_stricmp( token, "color" ) ) { color[0] = atof( COM_Parse( &s ) ); clamp( color[0], 0, 1 ); color[1] = atof( COM_Parse( &s ) ); clamp( color[1], 0, 1 ); color[2] = atof( COM_Parse( &s ) ); clamp( color[2], 0, 1 ); color[3] = atof( COM_Parse( &s ) ); clamp( color[3], 0, 1 ); } else if( !Q_stricmp( token, "flashColor" ) ) { flashColor[0] = atof( COM_Parse( &s ) ); clamp( flashColor[0], 0, 1 ); flashColor[1] = atof( COM_Parse( &s ) ); clamp( flashColor[1], 0, 1 ); flashColor[2] = atof( COM_Parse( &s ) ); clamp( flashColor[2], 0, 1 ); flashColor[3] = atof( COM_Parse( &s ) ); clamp( flashColor[3], 0, 1 ); } else if( !Q_stricmp( token, "pic" ) ) { // draw a pic from a stat number value = SCR_ParseValue( &s ); if( value < 0 || value >= MAX_IMAGES ) CG_Error( "Pic >= MAX_IMAGES" ); if( cgs.configStrings[CS_IMAGES + value][0] ) { x = CG_HorizontalAlignForWidth( x, align, w ); y = CG_VerticalAlignForHeight( y, align, h ); trap_R_DrawStretchPic( x, y, w, h, 0, 0, 1, 1, color, trap_R_RegisterPic(cgs.configStrings[CS_IMAGES+value]) ); } } else if( !Q_stricmp( token, "picn" ) ) { // draw a pic from a name x = CG_HorizontalAlignForWidth( x, align, w ); y = CG_VerticalAlignForHeight( y, align, h ); trap_R_DrawStretchPic ( x, y, w, h, 0, 0, 1, 1, colorWhite, trap_R_RegisterPic( COM_Parse ( &s ) ) ); } else if( !Q_stricmp( token, "model" ) ) { // draw a model from a stat number struct model_s *model; value = SCR_ParseValue( &s ); if( value < 0 || value >= MAX_MODELS ) CG_Error( "Model >= MAX_MODELS" ); model = value > 1 ? CG_RegisterModel( cgs.configStrings[CS_MODELS+value] ) : NULL; // skelmod CG_DrawHUDModel( x, y, align, w, h, model, NULL, atof( COM_Parse ( &s ) ) ); } else if( !Q_stricmp( token, "modeln" ) ) {// draw a model from a name struct model_s *model; struct shader_s *shader; model = CG_RegisterModel( COM_Parse( &s ) ); // skelmod token = COM_Parse( &s ); shader = Q_stricmp( token, "NULL" ) ? trap_R_RegisterPic ( token ) : NULL; CG_DrawHUDModel( x, y, align, w, h, model, shader, atof( COM_Parse ( &s ) ) ); } else if( !Q_stricmp( token, "num" ) ) { // draw a number width = SCR_ParseValue( &s ); value = SCR_ParseValue( &s ); CG_DrawHUDField( x, y, align, flash ? flashColor : color, 32, width, value ); } else if( !Q_stricmp( token, "num2" ) ) { // draw a number width = SCR_ParseValue( &s ); value = SCR_ParseValue( &s ); CG_DrawHUDField( x, y, align, flash ? flashColor : color, 16, width, value ); } else if( !Q_stricmp( token, "curtime" ) ) { // draw a time value char str[32]; int m, sec, d; value = cg.frame.playerState.stats[STAT_RACE_TIME]; if (value) { // NO MORE WORKING //if (cg.frame.playerState.stats[STAT_RACE_STARTED] ) value = (cg.time/100) - value; d = value % 10; sec = (value / 10) % 60; m = (value / 600); Q_snprintfz( str, 32, "%02i:%02i:%01i", m, sec, d ); trap_SCR_DrawString( x, y, align, str, cgs.fontSystemMedium, colorWhite ); } } else if( !Q_stricmp( token, "matchbesttime" ) ) { // draw a time value char str[32]; int m, sec, d; value = cg.frame.playerState.stats[STAT_RACE_MATCHBESTTIME]; if (value) { d = value % 10; sec = (value / 10) % 60; m = (value / 600); Q_snprintfz( str, 32, "%02i:%02i:%01i", m, sec, d ); trap_SCR_DrawString( x, y, align, str, cgs.fontSystemMedium, colorWhite ); } } else if( !Q_stricmp( token, "plrbesttime" ) ) { // draw a time value char str[32]; int m, sec, d; value = cg.frame.playerState.stats[STAT_RACE_PLAYERBESTTIME]; if (value) { d = value % 10; sec = (value / 10) % 60; m = (value / 600); Q_snprintfz( str, 32, "%02i:%02i:%01i", m, sec, d ); trap_SCR_DrawString( x, y, align, str, cgs.fontSystemMedium, colorWhite ); } } else if( !Q_stricmp( token, "speed" ) ) { CG_DrawSpeed( x, y, align, cgs.fontSystemMedium, flash ? flashColor : color ); } else if( !Q_stricmp( token, "fps" ) ) { CG_DrawFPS( x, y, align, cgs.fontSystemMedium, flash ? flashColor : color ); } else if( !Q_stricmp( token, "clock" ) ) { CG_DrawClock( x, y, align, cgs.fontSystemMedium, flash ? flashColor : color ); } else if( !Q_stricmp( token, "match_message" ) && cg_showhelp->integer ) { trap_SCR_DrawString( x, y, align, cg.matchmessage, cgs.fontSystemMedium, flash ? flashColor : color ); } else if( !Q_stricmp( token, "pointing" ) ) { CG_DrawPointed( x, y, align, cgs.fontSystemMedium, flash ? flashColor : color ); } else if( !Q_stricmp( token, "stat_string" ) ) { index = SCR_ParseValue( &s ); if( index < 0 || index >= MAX_CONFIGSTRINGS ) CG_Error( "Bad stat_string index" ); trap_SCR_DrawString( x, y, align, cgs.configStrings[index], cgs.fontSystemMedium, flash ? flashColor : color ); } else if( !Q_stricmp( token, "string" ) ) { trap_SCR_DrawString( x, y, align, COM_Parse( &s ), cgs.fontSystemMedium, flash ? flashColor : color ); } else if( !Q_stricmp( token, "rect" ) ) { CG_DrawHUDRect( x, y, align, w, h, SCR_ParseValue( &s ), SCR_ParseValue( &s ), flash ? flashColor : color ); // ByMiK } else if( !Q_stricmp( token, "weaponlist" ) ) { // TODO: align support int ix, iy, wx, wy, sx, sy; ix = (int)(atoi ( COM_Parse ( &s ) ) * cgs.vidWidth/800 ); iy = (int)(atoi ( COM_Parse ( &s ) ) * cgs.vidHeight/600 ); wx = (int)(atoi ( COM_Parse ( &s ) ) * cgs.vidWidth/800 ); wy = (int)(atoi ( COM_Parse ( &s ) ) * cgs.vidHeight/600 ); sx = (int)(atoi ( COM_Parse ( &s ) ) * cgs.vidWidth/800 ); sy = (int)(atoi ( COM_Parse ( &s ) ) * cgs.vidHeight/600 ); SCR_DrawWeaponList( x, y, ix, iy, wx, wy, sx, sy ); } else if( !Q_stricmp( token, "playername" ) ) { // wsw : jal value = SCR_ParseValue( &s ); if( value > 0 && value <= MAX_CLIENTS && cgs.clientInfo[value-1].name ) trap_SCR_DrawString( x, y, align, cgs.clientInfo[value-1].name, cgs.fontSystemMedium, flash ? flashColor : color ); } } } } /* ================ SCR_DrawStats The status bar is a small layout program that is based on the stats array ================ */ void SCR_DrawStats( void ) { if( !cg_showHUD->integer ) return; CG_ExecuteLayoutProgram( cg.statusBar ); } /* ================ SCR_DrawLayout ================ */ void SCR_DrawLayout( void ) { SCR_ExecuteLayoutString( cg.layout ); } /* ================ SCR_DrawInventory ================ */ #define DISPLAY_ITEMS 17 void SCR_DrawInventory( void ) { int i, j; int num, selected_num, item; int index[MAX_ITEMS]; char string[1024]; int x, y; char binding[1024]; char *bind, *buf; int selected; int top; int lineHeight = trap_SCR_strHeight( cgs.fontSystemSmall ); selected = cg.frame.playerState.stats[STAT_SELECTED_ITEM]; num = 0; selected_num = 0; for( i = 0; i < MAX_ITEMS; i++ ) { if( i == selected ) selected_num = num; if( cg.inventory[i] ) { index[num] = i; num++; } } // determine scroll point top = selected_num - DISPLAY_ITEMS / 2; if( num - top < DISPLAY_ITEMS ) top = num - DISPLAY_ITEMS; if( top < 0 ) top = 0; x = (cgs.vidWidth-256) / 2; y = (cgs.vidHeight-240) / 2; y += 24; x += 24; trap_SCR_DrawString( x, y, ALIGN_LEFT_TOP, "hotkey ### item", cgs.fontSystemSmall, colorWhite ); trap_SCR_DrawString( x, y+lineHeight, ALIGN_LEFT_TOP, "------ --- ----", cgs.fontSystemSmall, colorWhite ); y += lineHeight * 2; for( i = top; i < num && i < top+DISPLAY_ITEMS; i++ ) { item = index[i]; // search for a binding Q_snprintfz( binding, sizeof(binding), "use %s", cgs.configStrings[CS_ITEMS+item] ); bind = ""; for( j = 0; j < 256; j++ ) { buf = trap_Key_GetBindingBuf( j ); if( buf && !Q_stricmp( buf, binding ) ) { bind = trap_Key_KeynumToString( j ); break; } } Q_snprintfz( string, sizeof(string), "%6s %3i %s", bind, cg.inventory[item], cgs.configStrings[CS_ITEMS+item] ); if ( item != selected ) { trap_SCR_DrawString( x, y, ALIGN_LEFT_TOP, string, cgs.fontSystemSmall, colorWhite ); } else { // draw a blinky cursor by the selected item if ( (int)(cg.realTime*10) & 1 ) trap_SCR_DrawString( x-trap_SCR_strWidth("*",cgs.fontSystemSmall,1), y, ALIGN_LEFT_TOP, "*", cgs.fontSystemSmall, colorYellow ); trap_SCR_DrawString( x, y, ALIGN_LEFT_TOP, string, cgs.fontSystemSmall, colorYellow ); } y += lineHeight; } } //======================================================= void SCR_DrawScoreboard( void ); // wsw : jal : predefined layouts for scoreboards /* ================== SCR_Draw2D ================== */ void SCR_Draw2D( void ) { if( !cg_draw2D->integer ) return; if( cg_clientHUD->modified ) { CG_LoadStatusBar(); cg_clientHUD->modified = qfalse; } SCR_DrawQuadEffect(); SCR_DrawPlayerNames(); SCR_DrawStats(); if( cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_GENERIC ) SCR_DrawLayout(); if( cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_INVENTORY ) SCR_DrawInventory(); if( (cg.frame.playerState.stats[STAT_LAYOUTS] & STAT_LAYOUT_SCOREBOARD && !cg.demoPlaying) || (cg.demoPlaying && (cg.demoShowScoreboard || cg.frame.match.state > MATCH_STATE_PLAYTIME || (cg.frame.playerState.pmove.pm_type != PM_SPECTATOR && cg.frame.playerState.stats[STAT_HEALTH] <= 0))) ) SCR_DrawScoreboard (); SCR_CheckDrawCenterString(); }