// Copyright (C) 1999-2000 Id Software, Inc. // // cg_draw.c -- draw all of the graphical elements during // active (after loading) gameplay #include "cg_local.h" #include "../ui/ui_shared.h" qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y); qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle ); // used for scoreboard extern displayContextDef_t cgDC; menuDef_t *menuScoreboard = NULL; vec4_t bluehudtint = {0.5, 0.5, 1.0, 1.0}; vec4_t redhudtint = {1.0, 0.5, 0.5, 1.0}; float *hudTintColor; int sortedTeamPlayers[TEAM_MAXOVERLAY]; int numSortedTeamPlayers; int lastvalidlockdif; extern float zoomFov; //this has to be global client-side char systemChat[256]; char teamChat1[256]; char teamChat2[256]; char *showPowersName[] = { "Heal",//FP_HEAL "Jump",//FP_LEVITATION "Speed",//FP_SPEED "Push",//FP_PUSH "Pull",//FP_PULL "Mind Trick",//FP_TELEPTAHY "Grip",//FP_GRIP "Lightning",//FP_LIGHTNING "Dark Rage",//FP_RAGE "Protect",//FP_PROTECT "Absorb",//FP_ABSORB "Team Heal",//FP_TEAM_HEAL "Team Replenish",//FP_TEAM_FORCE "Drain",//FP_DRAIN "Seeing",//FP_SEE "Saber Attack",//FP_SABERATTACK "Saber Defend",//FP_SABERDEFEND "Saber Throw",//FP_SABERTHROW NULL }; int MenuFontToHandle(int iMenuFont) { switch (iMenuFont) { case FONT_SMALL: return cgDC.Assets.qhSmallFont; case FONT_MEDIUM: return cgDC.Assets.qhMediumFont; case FONT_LARGE: return cgDC.Assets.qhBigFont; } return cgDC.Assets.qhMediumFont; } int CG_Text_Width(const char *text, float scale, int iMenuFont) { int iFontIndex = MenuFontToHandle(iMenuFont); return trap_R_Font_StrLenPixels(text, iFontIndex, scale); } int CG_Text_Height(const char *text, float scale, int iMenuFont) { int iFontIndex = MenuFontToHandle(iMenuFont); return trap_R_Font_HeightPixels(iFontIndex, scale); } #include "../qcommon/qfiles.h" // for STYLE_BLINK etc void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style, int iMenuFont) { int iStyleOR = 0; int iFontIndex = MenuFontToHandle(iMenuFont); switch (style) { case ITEM_TEXTSTYLE_NORMAL: iStyleOR = 0;break; // JK2 normal text case ITEM_TEXTSTYLE_BLINK: iStyleOR = STYLE_BLINK;break; // JK2 fast blinking case ITEM_TEXTSTYLE_PULSE: iStyleOR = STYLE_BLINK;break; // JK2 slow pulsing case ITEM_TEXTSTYLE_SHADOWED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) case ITEM_TEXTSTYLE_OUTLINED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) case ITEM_TEXTSTYLE_OUTLINESHADOWED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) case ITEM_TEXTSTYLE_SHADOWEDMORE: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this ) } trap_R_Font_DrawString( x, // int ox y, // int oy text, // const char *text color, // paletteRGBA_c c iStyleOR | iFontIndex, // const int iFontHandle !limit?-1:limit, // iCharLimit (-1 = none) scale // const float scale = 1.0f ); } /* qboolean CG_WorldCoordToScreenCoord(vec3_t worldCoord, int *x, int *y) Take any world coord and convert it to a 2D virtual 640x480 screen coord */ /* qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y) { int xcenter, ycenter; vec3_t local, transformed; // xcenter = cg.refdef.width / 2;//gives screen coords adjusted for resolution // ycenter = cg.refdef.height / 2;//gives screen coords adjusted for resolution //NOTE: did it this way because most draw functions expect virtual 640x480 coords // and adjust them for current resolution xcenter = 640 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn ycenter = 480 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn VectorSubtract (worldCoord, cg.refdef.vieworg, local); transformed[0] = DotProduct(local,vright); transformed[1] = DotProduct(local,vup); transformed[2] = DotProduct(local,vfwd); // Make sure Z is not negative. if(transformed[2] < 0.01) { return qfalse; } // Simple convert to screen coords. float xzi = xcenter / transformed[2] * (90.0/cg.refdef.fov_x); float yzi = ycenter / transformed[2] * (90.0/cg.refdef.fov_y); *x = xcenter + xzi * transformed[0]; *y = ycenter - yzi * transformed[1]; return qtrue; } qboolean CG_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y ) { float xF, yF; qboolean retVal = CG_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF ); *x = (int)xF; *y = (int)yF; return retVal; } */ /* ================ CG_DrawZoomMask ================ */ static void CG_DrawZoomMask( void ) { vec4_t color1; float level; static qboolean flip = qtrue; // int ammo = cg_entities[0].gent->client->ps.ammo[weaponData[cent->currentState.weapon].ammoIndex]; float cx, cy; // int val[5]; float max, fi; // Check for Binocular specific zooming since we'll want to render different bits in each case if ( cg.predictedPlayerState.zoomMode == 2 ) { int val, i; float off; // zoom level level = (float)(80.0f - cg.predictedPlayerState.zoomFov) / 80.0f; // ...so we'll clamp it if ( level < 0.0f ) { level = 0.0f; } else if ( level > 1.0f ) { level = 1.0f; } // Using a magic number to convert the zoom level to scale amount level *= 162.0f; // draw blue tinted distortion mask, trying to make it as small as is necessary to fill in the viewable area trap_R_SetColor( colorTable[CT_WHITE] ); CG_DrawPic( 34, 48, 570, 362, cgs.media.binocularStatic ); // Black out the area behind the numbers trap_R_SetColor( colorTable[CT_BLACK]); CG_DrawPic( 212, 367, 200, 40, cgs.media.whiteShader ); // Numbers should be kind of greenish color1[0] = 0.2f; color1[1] = 0.4f; color1[2] = 0.2f; color1[3] = 0.3f; trap_R_SetColor( color1 ); // Draw scrolling numbers, use intervals 10 units apart--sorry, this section of code is just kind of hacked // up with a bunch of magic numbers..... val = ((int)((cg.refdefViewAngles[YAW] + 180) / 10)) * 10; off = (cg.refdefViewAngles[YAW] + 180) - val; for ( i = -10; i < 30; i += 10 ) { val -= 10; if ( val < 0 ) { val += 360; } // we only want to draw the very far left one some of the time, if it's too far to the left it will // poke outside the mask. if (( off > 3.0f && i == -10 ) || i > -10 ) { // draw the value, but add 200 just to bump the range up...arbitrary, so change it if you like CG_DrawNumField( 155 + i * 10 + off * 10, 374, 3, val + 200, 24, 14, NUM_FONT_CHUNKY, qtrue ); CG_DrawPic( 245 + (i-1) * 10 + off * 10, 376, 6, 6, cgs.media.whiteShader ); } } CG_DrawPic( 212, 367, 200, 28, cgs.media.binocularOverlay ); color1[0] = sin( cg.time * 0.01f ) * 0.5f + 0.5f; color1[0] = color1[0] * color1[0]; color1[1] = color1[0]; color1[2] = color1[0]; color1[3] = 1.0f; trap_R_SetColor( color1 ); CG_DrawPic( 82, 94, 16, 16, cgs.media.binocularCircle ); // Flickery color color1[0] = 0.7f + crandom() * 0.1f; color1[1] = 0.8f + crandom() * 0.1f; color1[2] = 0.7f + crandom() * 0.1f; color1[3] = 1.0f; trap_R_SetColor( color1 ); CG_DrawPic( 0, 0, 640, 480, cgs.media.binocularMask ); CG_DrawPic( 4, 282 - level, 16, 16, cgs.media.binocularArrow ); // The top triangle bit randomly flips if ( flip ) { CG_DrawPic( 330, 60, -26, -30, cgs.media.binocularTri ); } else { CG_DrawPic( 307, 40, 26, 30, cgs.media.binocularTri ); } if ( random() > 0.98f && ( cg.time & 1024 )) { flip = !flip; } } else if ( cg.predictedPlayerState.zoomMode) { // disruptor zoom mode level = (float)(50.0f - zoomFov) / 50.0f;//(float)(80.0f - zoomFov) / 80.0f; // ...so we'll clamp it if ( level < 0.0f ) { level = 0.0f; } else if ( level > 1.0f ) { level = 1.0f; } // Using a magic number to convert the zoom level to a rotation amount that correlates more or less with the zoom artwork. level *= 103.0f; // Draw target mask trap_R_SetColor( colorTable[CT_WHITE] ); CG_DrawPic( 0, 0, 640, 480, cgs.media.disruptorMask ); // apparently 99.0f is the full zoom level if ( level >= 99 ) { // Fully zoomed, so make the rotating insert pulse color1[0] = 1.0f; color1[1] = 1.0f; color1[2] = 1.0f; color1[3] = 0.7f + sin( cg.time * 0.01f ) * 0.3f; trap_R_SetColor( color1 ); } // Draw rotating insert CG_DrawRotatePic2( 320, 240, 640, 480, -level, cgs.media.disruptorInsert ); // Increase the light levels under the center of the target // CG_DrawPic( 198, 118, 246, 246, cgs.media.disruptorLight ); // weirdness.....converting ammo to a base five number scale just to be geeky. /* val[0] = ammo % 5; val[1] = (ammo / 5) % 5; val[2] = (ammo / 25) % 5; val[3] = (ammo / 125) % 5; val[4] = (ammo / 625) % 5; color1[0] = 0.2f; color1[1] = 0.55f + crandom() * 0.1f; color1[2] = 0.5f + crandom() * 0.1f; color1[3] = 1.0f; trap_R_SetColor( color1 ); for ( int t = 0; t < 5; t++ ) { cx = 320 + sin( (t*10+45)/57.296f ) * 192; cy = 240 + cos( (t*10+45)/57.296f ) * 192; CG_DrawRotatePic2( cx, cy, 24, 38, 45 - t * 10, trap_R_RegisterShader( va("gfx/2d/char%d",val[4-t] ))); } */ //max = ( cg_entities[0].gent->health / 100.0f ); max = cg.snap->ps.ammo[weaponData[WP_DISRUPTOR].ammoIndex] / (float)ammoData[weaponData[WP_DISRUPTOR].ammoIndex].max; if ( max > 1.0f ) { max = 1.0f; } color1[0] = (1.0f - max) * 2.0f; color1[1] = max * 1.5f; color1[2] = 0.0f; color1[3] = 1.0f; // If we are low on health, make us flash if ( max < 0.15f && ( cg.time & 512 )) { VectorClear( color1 ); } if ( color1[0] > 1.0f ) { color1[0] = 1.0f; } if ( color1[1] > 1.0f ) { color1[1] = 1.0f; } trap_R_SetColor( color1 ); max *= 58.0f; for (fi = 18.5f; fi <= 18.5f + max; fi+= 3 ) // going from 15 to 45 degrees, with 5 degree increments { cx = 320 + sin( (fi+90.0f)/57.296f ) * 190; cy = 240 + cos( (fi+90.0f)/57.296f ) * 190; CG_DrawRotatePic2( cx, cy, 12, 24, 90 - fi, cgs.media.disruptorInsertTick ); } if ( cg.predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT ) { trap_R_SetColor( colorTable[CT_WHITE] ); // draw the charge level max = ( cg.time - cg.predictedPlayerState.weaponChargeTime ) / ( 50.0f * 30.0f ); // bad hardcodedness 50 is disruptor charge unit and 30 is max charge units allowed. if ( max > 1.0f ) { max = 1.0f; } trap_R_DrawStretchPic(257, 435, 134*max, 34, 0, 0, max, 1, cgs.media.disruptorChargeShader); } // trap_R_SetColor( colorTable[CT_WHITE] ); // CG_DrawPic( 0, 0, 640, 480, cgs.media.disruptorMask ); } } /* ================ CG_Draw3DModel ================ */ void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles ) { refdef_t refdef; refEntity_t ent; if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) { return; } memset( &refdef, 0, sizeof( refdef ) ); memset( &ent, 0, sizeof( ent ) ); AnglesToAxis( angles, ent.axis ); VectorCopy( origin, ent.origin ); ent.hModel = model; ent.customSkin = skin; ent.renderfx = RF_NOSHADOW; // no stencil shadows refdef.rdflags = RDF_NOWORLDMODEL; AxisClear( refdef.viewaxis ); refdef.fov_x = 30; refdef.fov_y = 30; refdef.x = x; refdef.y = y; refdef.width = w; refdef.height = h; refdef.time = cg.time; trap_R_ClearScene(); trap_R_AddRefEntityToScene( &ent ); trap_R_RenderScene( &refdef ); } /* ================ CG_DrawHead Used for both the status bar and the scoreboard ================ */ void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles ) { clientInfo_t *ci; ci = &cgs.clientinfo[ clientNum ]; CG_DrawPic( x, y, w, h, ci->modelIcon ); // if they are deferred, draw a cross out if ( ci->deferred ) { CG_DrawPic( x, y, w, h, cgs.media.deferShader ); } } /* ================ CG_DrawFlagModel Used for both the status bar and the scoreboard ================ */ void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ) { qhandle_t cm; float len; vec3_t origin, angles; vec3_t mins, maxs; qhandle_t handle; if ( !force2D && cg_draw3dIcons.integer ) { VectorClear( angles ); cm = cgs.media.redFlagModel; // offset the origin y and z to center the flag trap_R_ModelBounds( cm, mins, maxs ); origin[2] = -0.5 * ( mins[2] + maxs[2] ); origin[1] = 0.5 * ( mins[1] + maxs[1] ); // calculate distance so the flag nearly fills the box // assume heads are taller than wide len = 0.5 * ( maxs[2] - mins[2] ); origin[0] = len / 0.268; // len / tan( fov/2 ) angles[YAW] = 60 * sin( cg.time / 2000.0 );; if( team == TEAM_RED ) { handle = cgs.media.redFlagModel; } else if( team == TEAM_BLUE ) { handle = cgs.media.blueFlagModel; } else if( team == TEAM_FREE ) { handle = cgs.media.neutralFlagModel; } else { return; } CG_Draw3DModel( x, y, w, h, handle, 0, origin, angles ); } else if ( cg_drawIcons.integer ) { gitem_t *item; if( team == TEAM_RED ) { item = BG_FindItemForPowerup( PW_REDFLAG ); } else if( team == TEAM_BLUE ) { item = BG_FindItemForPowerup( PW_BLUEFLAG ); } else if( team == TEAM_FREE ) { item = BG_FindItemForPowerup( PW_NEUTRALFLAG ); } else { return; } if (item) { CG_DrawPic( x, y, w, h, cg_items[ ITEM_INDEX(item) ].icon ); } } } /* ================ DrawAmmo ================ */ void DrawAmmo() { int x, y; x = SCREEN_WIDTH-80; y = SCREEN_HEIGHT-80; } /* ================ CG_DrawHUDLeftFrame1 ================ */ void CG_DrawHUDLeftFrame1(int x,int y) { // Inner gray wire frame trap_R_SetColor( hudTintColor ); CG_DrawPic( x, y, 80, 80, cgs.media.HUDInnerLeft ); } /* ================ CG_DrawHUDLeftFrame2 ================ */ void CG_DrawHUDLeftFrame2(int x,int y) { // Inner gray wire frame trap_R_SetColor( hudTintColor ); CG_DrawPic( x, y, 80, 80, cgs.media.HUDLeftFrame ); // Metal frame } /* ================ DrawHealthArmor ================ */ void DrawHealthArmor(int x,int y) { vec4_t calcColor; float armorPercent,hold,healthPercent; playerState_t *ps; int healthAmt; int armorAmt; ps = &cg.snap->ps; healthAmt = ps->stats[STAT_HEALTH]; armorAmt = ps->stats[STAT_ARMOR]; if (healthAmt > ps->stats[STAT_MAX_HEALTH]) { healthAmt = ps->stats[STAT_MAX_HEALTH]; } if (armorAmt > 100) { armorAmt = 100; } trap_R_SetColor( colorTable[CT_WHITE] ); CG_DrawPic( x, y, 80, 80, cgs.media.HUDLeftFrame ); // Circular black background // Outer Armor circular memcpy(calcColor, colorTable[CT_GREEN], sizeof(vec4_t)); hold = armorAmt-(ps->stats[STAT_MAX_HEALTH]/2); armorPercent = (float) hold/(ps->stats[STAT_MAX_HEALTH]/2); if (armorPercent <0) { armorPercent = 0; } calcColor[0] *= armorPercent; calcColor[1] *= armorPercent; calcColor[2] *= armorPercent; trap_R_SetColor( calcColor); CG_DrawPic( x, y, 80, 80, cgs.media.HUDArmor1 ); // Inner Armor circular if (armorPercent>0) { armorPercent = 1; } else { armorPercent = (float) armorAmt/(ps->stats[STAT_MAX_HEALTH]/2); } memcpy(calcColor, colorTable[CT_GREEN], sizeof(vec4_t)); calcColor[0] *= armorPercent; calcColor[1] *= armorPercent; calcColor[2] *= armorPercent; trap_R_SetColor( calcColor); CG_DrawPic( x, y, 80, 80, cgs.media.HUDArmor2 ); // Inner Armor circular if (ps->stats[STAT_ARMOR]) // Is there armor? Draw the HUD Armor TIC { // Make tic flash if inner armor is at 50% (25% of full armor) if (armorPercent<.5) // Do whatever the flash timer says { if (cg.HUDTickFlashTime < cg.time) // Flip at the same time { cg.HUDTickFlashTime = cg.time + 100; if (cg.HUDArmorFlag) { cg.HUDArmorFlag = qfalse; } else { cg.HUDArmorFlag = qtrue; } } } else { cg.HUDArmorFlag=qtrue; } } else // No armor? Don't show it. { cg.HUDArmorFlag=qfalse; } memcpy(calcColor, colorTable[CT_RED], sizeof(vec4_t)); healthPercent = (float) healthAmt/ps->stats[STAT_MAX_HEALTH]; calcColor[0] *= healthPercent; calcColor[1] *= healthPercent; calcColor[2] *= healthPercent; trap_R_SetColor( calcColor); CG_DrawPic( x, y, 80, 80, cgs.media.HUDHealth ); // Make tic flash if health is at 20% of full if (healthPercent>.20) { cg.HUDHealthFlag=qtrue; } else { if (cg.HUDTickFlashTime < cg.time) // Flip at the same time { cg.HUDTickFlashTime = cg.time + 100; if ((armorPercent>0) && (armorPercent<.5)) // Keep the tics in sync if flashing { cg.HUDHealthFlag=cg.HUDArmorFlag; } else { if (cg.HUDHealthFlag) { cg.HUDHealthFlag = qfalse; } else { cg.HUDHealthFlag = qtrue; } } } } // Draw the ticks if (cg.HUDHealthFlag) { trap_R_SetColor( colorTable[CT_RED] ); CG_DrawPic( x, y, 80, 80, cgs.media.HUDHealthTic ); } if (cg.HUDArmorFlag) { trap_R_SetColor( colorTable[CT_GREEN] ); CG_DrawPic( x, y, 80, 80, cgs.media.HUDArmorTic ); // } trap_R_SetColor(hudTintColor); CG_DrawPic( x, y, 80, 80, cgs.media.HUDLeftStatic ); // trap_R_SetColor( colorTable[CT_RED] ); CG_DrawNumField (x + 16, y + 40, 3, ps->stats[STAT_HEALTH], 14, 18, NUM_FONT_SMALL,qfalse); trap_R_SetColor( colorTable[CT_GREEN] ); CG_DrawNumField (x + 18 + 14, y + 40 + 14, 3, ps->stats[STAT_ARMOR], 14, 18, NUM_FONT_SMALL,qfalse); trap_R_SetColor(hudTintColor ); CG_DrawPic( x, y, 80, 80, cgs.media.HUDLeft ); // Metal frame } /* ================ CG_DrawHealth ================ */ void CG_DrawHealth(int x,int y) { vec4_t calcColor; float healthPercent; playerState_t *ps; int healthAmt; ps = &cg.snap->ps; healthAmt = ps->stats[STAT_HEALTH]; if (healthAmt > ps->stats[STAT_MAX_HEALTH]) { healthAmt = ps->stats[STAT_MAX_HEALTH]; } memcpy(calcColor, colorTable[CT_HUD_RED], sizeof(vec4_t)); healthPercent = (float) healthAmt/ps->stats[STAT_MAX_HEALTH]; calcColor[0] *= healthPercent; calcColor[1] *= healthPercent; calcColor[2] *= healthPercent; trap_R_SetColor( calcColor); CG_DrawPic( x, y, 80, 80, cgs.media.HUDHealth ); // Draw the ticks if (cg.HUDHealthFlag) { trap_R_SetColor( colorTable[CT_HUD_RED] ); CG_DrawPic( x, y, 80, 80, cgs.media.HUDHealthTic ); } trap_R_SetColor( colorTable[CT_HUD_RED] ); CG_DrawNumField (x + 16, y + 40, 3, ps->stats[STAT_HEALTH], 6, 12, NUM_FONT_SMALL,qfalse); } /* ================ CG_DrawArmor ================ */ void CG_DrawArmor(int x,int y) { vec4_t calcColor; float armorPercent,hold; playerState_t *ps; int armor; ps = &cg.snap->ps; // Outer Armor circular memcpy(calcColor, colorTable[CT_HUD_GREEN], sizeof(vec4_t)); armor =ps->stats[STAT_ARMOR]; if (armor> ps->stats[STAT_MAX_HEALTH]) { armor = ps->stats[STAT_MAX_HEALTH]; } hold = armor-(ps->stats[STAT_MAX_HEALTH]/2); armorPercent = (float) hold/(ps->stats[STAT_MAX_HEALTH]/2); if (armorPercent <0) { armorPercent = 0; } calcColor[0] *= armorPercent; calcColor[1] *= armorPercent; calcColor[2] *= armorPercent; trap_R_SetColor( calcColor); CG_DrawPic( x, y, 80, 80, cgs.media.HUDArmor1 ); // Inner Armor circular if (armorPercent>0) { armorPercent = 1; } else { armorPercent = (float) ps->stats[STAT_ARMOR]/(ps->stats[STAT_MAX_HEALTH]/2); } memcpy(calcColor, colorTable[CT_HUD_GREEN], sizeof(vec4_t)); calcColor[0] *= armorPercent; calcColor[1] *= armorPercent; calcColor[2] *= armorPercent; trap_R_SetColor( calcColor); CG_DrawPic( x, y, 80, 80, cgs.media.HUDArmor2 ); // Inner Armor circular if (ps->stats[STAT_ARMOR]) // Is there armor? Draw the HUD Armor TIC { // Make tic flash if inner armor is at 50% (25% of full armor) if (armorPercent<.5) // Do whatever the flash timer says { if (cg.HUDTickFlashTime < cg.time) // Flip at the same time { cg.HUDTickFlashTime = cg.time + 100; if (cg.HUDArmorFlag) { cg.HUDArmorFlag = qfalse; } else { cg.HUDArmorFlag = qtrue; } } } else { cg.HUDArmorFlag=qtrue; } } else // No armor? Don't show it. { cg.HUDArmorFlag=qfalse; } if (cg.HUDArmorFlag) { trap_R_SetColor( colorTable[CT_HUD_GREEN] ); CG_DrawPic( x, y, 80, 80, cgs.media.HUDArmorTic ); } trap_R_SetColor( colorTable[CT_HUD_GREEN] ); CG_DrawNumField (x + 18 + 14, y + 40 + 14, 3, ps->stats[STAT_ARMOR], 6, 12, NUM_FONT_SMALL,qfalse); } /* ================ CG_DrawHUDRightFrame1 ================ */ void CG_DrawHUDRightFrame1(int x,int y) { trap_R_SetColor( hudTintColor ); // Inner gray wire frame CG_DrawPic( x, y, 80, 80, cgs.media.HUDInnerRight ); // } /* ================ CG_DrawHUDRightFrame2 ================ */ void CG_DrawHUDRightFrame2(int x,int y) { trap_R_SetColor( hudTintColor ); CG_DrawPic( x, y, 80, 80, cgs.media.HUDRightFrame ); // Metal frame } /* ================ CG_DrawAmmo ================ */ static void CG_DrawAmmo(centity_t *cent,int x,int y) { playerState_t *ps; int numColor_i; int i; vec4_t calcColor; float value,inc,percent; ps = &cg.snap->ps; if (!cent->currentState.weapon ) // We don't have a weapon right now { return; } if ( cent->currentState.weapon == WP_SABER ) { trap_R_SetColor( colorTable[CT_WHITE] ); // don't need to draw ammo, but we will draw the current saber style in this window switch ( cg.predictedPlayerState.fd.saberDrawAnimLevel ) { case 1://FORCE_LEVEL_1: CG_DrawPic( x, y, 80, 40, cgs.media.HUDSaberStyle1 ); break; case 2://FORCE_LEVEL_2: CG_DrawPic( x, y, 80, 40, cgs.media.HUDSaberStyle2 ); break; case 3://FORCE_LEVEL_3: CG_DrawPic( x, y, 80, 40, cgs.media.HUDSaberStyle3 ); break; } return; } else { value = ps->ammo[weaponData[cent->currentState.weapon].ammoIndex]; } if (value < 0) // No ammo { return; } // // ammo // /* if (cg.oldammo < value) { cg.oldAmmoTime = cg.time + 200; } cg.oldammo = value; */ // Firing or reloading? /* if (( pm->ps->weaponstate == WEAPON_FIRING && cg.predictedPlayerState.weaponTime > 100 )) { numColor_i = CT_LTGREY; } */ // Overcharged? // else if ( cent->gent->s.powerups & ( 1 << PW_WEAPON_OVERCHARGE ) ) // { // numColor_i = CT_WHITE; // } // else // { // if ( value > 0 ) // { // if (cg.oldAmmoTime > cg.time) // { // numColor_i = CT_YELLOW; // } // else // { // numColor_i = CT_HUD_ORANGE; // } // } // else // { // numColor_i = CT_RED; // } // } numColor_i = CT_HUD_ORANGE; trap_R_SetColor( colorTable[numColor_i] ); CG_DrawNumField (x + 30, y + 26, 3, value, 6, 12, NUM_FONT_SMALL,qfalse); //cg.snap->ps.ammo[weaponData[cg.snap->ps.weapon].ammoIndex] inc = (float) ammoData[weaponData[cent->currentState.weapon].ammoIndex].max / MAX_TICS; value =ps->ammo[weaponData[cent->currentState.weapon].ammoIndex]; for (i=MAX_TICS-1;i>=0;i--) { if (value <= 0) // partial tic { memcpy(calcColor, colorTable[CT_BLACK], sizeof(vec4_t)); } else if (value < inc) // partial tic { memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t)); percent = value / inc; calcColor[0] *= percent; calcColor[1] *= percent; calcColor[2] *= percent; } else { memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t)); } trap_R_SetColor( calcColor); CG_DrawPic( x + ammoTicPos[i].x, y + ammoTicPos[i].y, ammoTicPos[i].width, ammoTicPos[i].height, ammoTicPos[i].tic ); value -= inc; } } /* ================ CG_DrawForcePower ================ */ void CG_DrawForcePower(int x,int y) { int i; vec4_t calcColor; float value,inc,percent; inc = (float) 100 / MAX_TICS; value = cg.snap->ps.fd.forcePower; for (i=MAX_TICS-1;i>=0;i--) { if (value <= 0) // partial tic { memcpy(calcColor, colorTable[CT_BLACK], sizeof(vec4_t)); } else if (value < inc) // partial tic { memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t)); percent = value / inc; calcColor[0] *= percent; calcColor[1] *= percent; calcColor[2] *= percent; } else { memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t)); } trap_R_SetColor( calcColor); CG_DrawPic( x + forceTicPos[i].x, y + forceTicPos[i].y, forceTicPos[i].width, forceTicPos[i].height, forceTicPos[i].tic ); value -= inc; } } /* ================ CG_DrawHUD ================ */ void CG_DrawHUD(centity_t *cent) { menuDef_t *menuHUD = NULL; const char *scoreStr = NULL; int scoreBias; char scoreBiasStr[16]; if (cg_hudFiles.integer) { int x = 0; int y = SCREEN_HEIGHT-80; char ammoString[64]; int weapX = x; UI_DrawProportionalString( x+16, y+40, va( "%i", cg.snap->ps.stats[STAT_HEALTH] ), UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_RED] ); UI_DrawProportionalString( x+18+14, y+40+14, va( "%i", cg.snap->ps.stats[STAT_ARMOR] ), UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_GREEN] ); if (cg.snap->ps.weapon == WP_SABER) { if (cg.snap->ps.fd.saberDrawAnimLevel == FORCE_LEVEL_3) { Com_sprintf(ammoString, sizeof(ammoString), "STRONG"); weapX += 16; } else if (cg.snap->ps.fd.saberDrawAnimLevel == FORCE_LEVEL_2) { Com_sprintf(ammoString, sizeof(ammoString), "MEDIUM"); weapX += 16; } else { Com_sprintf(ammoString, sizeof(ammoString), "FAST"); } } else { Com_sprintf(ammoString, sizeof(ammoString), "%i", cg.snap->ps.ammo[weaponData[cent->currentState.weapon].ammoIndex]); } UI_DrawProportionalString( SCREEN_WIDTH-(weapX+16+32), y+40, va( "%s", ammoString ), UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_ORANGE] ); UI_DrawProportionalString( SCREEN_WIDTH-(x+18+14+32), y+40+14, va( "%i", cg.snap->ps.fd.forcePower), UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_ICON_BLUE] ); return; } if (cgs.gametype >= GT_TEAM) { // tint the hud items based on team if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) hudTintColor = redhudtint; else if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) hudTintColor = bluehudtint; else // If we're not on a team for whatever reason, leave things as they are. hudTintColor = colorTable[CT_WHITE]; } else { // tint the hud items white (dont' tint) hudTintColor = colorTable[CT_WHITE]; } menuHUD = Menus_FindByName("lefthud"); if (menuHUD) { CG_DrawHUDLeftFrame1(menuHUD->window.rect.x,menuHUD->window.rect.y); CG_DrawArmor(menuHUD->window.rect.x,menuHUD->window.rect.y); CG_DrawHealth(menuHUD->window.rect.x,menuHUD->window.rect.y); CG_DrawHUDLeftFrame2(menuHUD->window.rect.x,menuHUD->window.rect.y); } else { //Apparently we failed to get proper coordinates from the menu, so resort to manually inputting them. CG_DrawHUDLeftFrame1(0,SCREEN_HEIGHT-80); CG_DrawArmor(0,SCREEN_HEIGHT-80); CG_DrawHealth(0,SCREEN_HEIGHT-80); CG_DrawHUDLeftFrame2(0,SCREEN_HEIGHT-80); } //scoreStr = va("Score: %i", cgs.clientinfo[cg.snap->ps.clientNum].score); if (0 && cgs.gametype < GT_TEAM ) { // This is a teamless mode, draw the score bias. scoreBias = cg.snap->ps.persistant[PERS_SCORE] - cgs.scores1; if (scoreBias == 0) { // We are the leader! if (cgs.scores2 <= 0) { // Nobody to be ahead of yet. Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), ""); } else { scoreBias = cg.snap->ps.persistant[PERS_SCORE] - cgs.scores2; if (scoreBias == 0) { Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), " (Tie)"); } else { Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), " (+%d)", scoreBias); } } } else // if (scoreBias < 0) { // We are behind! Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), " (%d)", scoreBias); } scoreStr = va("Score: %i%s", cg.snap->ps.persistant[PERS_SCORE], scoreBiasStr); } else { // Don't draw a bias. scoreStr = va("Score: %i", cg.snap->ps.persistant[PERS_SCORE]); } UI_DrawScaledProportionalString(SCREEN_WIDTH-124/*(strlen(scoreStr)*20.5)*/, SCREEN_HEIGHT-23, scoreStr, UI_RIGHT|UI_DROPSHADOW, colorTable[CT_WHITE], 0.7); menuHUD = Menus_FindByName("righthud"); if (menuHUD) { CG_DrawHUDRightFrame1(menuHUD->window.rect.x,menuHUD->window.rect.y); CG_DrawForcePower(menuHUD->window.rect.x,menuHUD->window.rect.y); CG_DrawAmmo(cent,menuHUD->window.rect.x,menuHUD->window.rect.y); CG_DrawHUDRightFrame2(menuHUD->window.rect.x,menuHUD->window.rect.y); } else { //Apparently we failed to get proper coordinates from the menu, so resort to manually inputting them. CG_DrawHUDRightFrame1(SCREEN_WIDTH-80,SCREEN_HEIGHT-80); CG_DrawForcePower(SCREEN_WIDTH-80,SCREEN_HEIGHT-80); CG_DrawAmmo(cent,SCREEN_WIDTH-80,SCREEN_HEIGHT-80); CG_DrawHUDRightFrame2(SCREEN_WIDTH-80,SCREEN_HEIGHT-80); } } #define MAX_SHOWPOWERS NUM_FORCE_POWERS qboolean ForcePower_Valid(int i) { if (i == FP_LEVITATION || i == FP_SABERATTACK || i == FP_SABERDEFEND || i == FP_SABERTHROW) { return qfalse; } if (cg.snap->ps.fd.forcePowersKnown & (1 << i)) { return qtrue; } return qfalse; } /* =================== CG_DrawForceSelect =================== */ void CG_DrawForceSelect( void ) { int i; int count; int smallIconSize,bigIconSize; int holdX,x,y,x2,y2,pad,length; int sideLeftIconCnt,sideRightIconCnt; int sideMax,holdCount,iconCnt; x2 = 0; y2 = 0; // don't display if dead if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { return; } if ((cg.forceSelectTime+WEAPON_SELECT_TIME)ps.fd.forcePowerSelected; return; } if (!cg.snap->ps.fd.forcePowersKnown) { return; } // count the number of powers owned count = 0; for (i=0;i < NUM_FORCE_POWERS;++i) { if (ForcePower_Valid(i)) { count++; } } if (count == 0) // If no force powers, don't display { return; } sideMax = 3; // Max number of icons on the side // Calculate how many icons will appear to either side of the center one holdCount = count - 1; // -1 for the center icon if (holdCount == 0) // No icons to either side { sideLeftIconCnt = 0; sideRightIconCnt = 0; } else if (count > (2*sideMax)) // Go to the max on each side { sideLeftIconCnt = sideMax; sideRightIconCnt = sideMax; } else // Less than max, so do the calc { sideLeftIconCnt = holdCount/2; sideRightIconCnt = holdCount - sideLeftIconCnt; } smallIconSize = 30; bigIconSize = 60; pad = 12; x = 320; y = 425; // Background length = (sideLeftIconCnt * smallIconSize) + (sideLeftIconCnt*pad) + bigIconSize + (sideRightIconCnt * smallIconSize) + (sideRightIconCnt*pad) + 12; i = BG_ProperForceIndex(cg.forceSelect) - 1; if (i < 0) { i = MAX_SHOWPOWERS; } trap_R_SetColor(NULL); // Work backwards from current icon holdX = x - ((bigIconSize/2) + pad + smallIconSize); for (iconCnt=1;iconCnt<(sideLeftIconCnt+1);i--) { if (i < 0) { i = MAX_SHOWPOWERS; } if (!ForcePower_Valid(forcePowerSorted[i])) // Does he have this power? { continue; } ++iconCnt; // Good icon if (cgs.media.forcePowerIcons[forcePowerSorted[i]]) { CG_DrawPic( holdX, y, smallIconSize, smallIconSize, cgs.media.forcePowerIcons[forcePowerSorted[i]] ); holdX -= (smallIconSize+pad); } } if (ForcePower_Valid(cg.forceSelect)) { // Current Center Icon if (cgs.media.forcePowerIcons[cg.forceSelect]) { CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2)), bigIconSize, bigIconSize, cgs.media.forcePowerIcons[cg.forceSelect] ); //only cache the icon for display } } i = BG_ProperForceIndex(cg.forceSelect) + 1; if (i>MAX_SHOWPOWERS) { i = 0; } // Work forwards from current icon holdX = x + (bigIconSize/2) + pad; for (iconCnt=1;iconCnt<(sideRightIconCnt+1);i++) { if (i>MAX_SHOWPOWERS) { i = 0; } if (!ForcePower_Valid(forcePowerSorted[i])) // Does he have this power? { continue; } ++iconCnt; // Good icon if (cgs.media.forcePowerIcons[forcePowerSorted[i]]) { CG_DrawPic( holdX, y, smallIconSize, smallIconSize, cgs.media.forcePowerIcons[forcePowerSorted[i]] ); //only cache the icon for display holdX += (smallIconSize+pad); } } if ( showPowersName[cg.forceSelect] ) { UI_DrawProportionalString(320, y + 30, showPowersName[cg.forceSelect], UI_CENTER | UI_SMALLFONT, colorTable[CT_ICON_BLUE]); } } /* =================== CG_DrawInventorySelect =================== */ void CG_DrawInvenSelect( void ) { int i; int sideMax,holdCount,iconCnt; int smallIconSize,bigIconSize; int sideLeftIconCnt,sideRightIconCnt; int count; int holdX,x,y,y2,pad; int height; float addX; // don't display if dead if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { return; } if ((cg.invenSelectTime+WEAPON_SELECT_TIME)ps.stats[STAT_HOLDABLE_ITEM] || !cg.snap->ps.stats[STAT_HOLDABLE_ITEMS]) { return; } if (cg.itemSelect == -1) { cg.itemSelect = bg_itemlist[cg.snap->ps.stats[STAT_HOLDABLE_ITEM]].giTag; } //const int bits = cg.snap->ps.stats[ STAT_ITEMS ]; // count the number of items owned count = 0; for ( i = 0 ; i < HI_NUM_HOLDABLE ; i++ ) { if (/*CG_InventorySelectable(i) && inv_icons[i]*/ (cg.snap->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << i)) ) { count++; } } if (!count) { y2 = 0; //err? UI_DrawProportionalString(320, y2 + 22, "EMPTY INVENTORY", UI_CENTER | UI_SMALLFONT, colorTable[CT_ICON_BLUE]); return; } sideMax = 3; // Max number of icons on the side // Calculate how many icons will appear to either side of the center one holdCount = count - 1; // -1 for the center icon if (holdCount == 0) // No icons to either side { sideLeftIconCnt = 0; sideRightIconCnt = 0; } else if (count > (2*sideMax)) // Go to the max on each side { sideLeftIconCnt = sideMax; sideRightIconCnt = sideMax; } else // Less than max, so do the calc { sideLeftIconCnt = holdCount/2; sideRightIconCnt = holdCount - sideLeftIconCnt; } i = cg.itemSelect - 1; if (i<0) { i = HI_NUM_HOLDABLE-1; } smallIconSize = 40; bigIconSize = 80; pad = 16; x = 320; y = 410; // Left side ICONS // Work backwards from current icon holdX = x - ((bigIconSize/2) + pad + smallIconSize); height = smallIconSize * cg.iconHUDPercent; addX = (float) smallIconSize * .75; for (iconCnt=0;iconCntps.stats[STAT_HOLDABLE_ITEMS] & (1 << i)) || i == cg.itemSelect ) { continue; } ++iconCnt; // Good icon if (cgs.media.invenIcons[i]) { trap_R_SetColor(NULL); CG_DrawPic( holdX, y+10, smallIconSize, smallIconSize, cgs.media.invenIcons[i] ); trap_R_SetColor(colorTable[CT_ICON_BLUE]); /*CG_DrawNumField (holdX + addX, y + smallIconSize, 2, cg.snap->ps.inventory[i], 6, 12, NUM_FONT_SMALL,qfalse); */ holdX -= (smallIconSize+pad); } } // Current Center Icon height = bigIconSize * cg.iconHUDPercent; if (cgs.media.invenIcons[cg.itemSelect]) { int itemNdex; trap_R_SetColor(NULL); CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10, bigIconSize, bigIconSize, cgs.media.invenIcons[cg.itemSelect] ); addX = (float) bigIconSize * .75; trap_R_SetColor(colorTable[CT_ICON_BLUE]); /*CG_DrawNumField ((x-(bigIconSize/2)) + addX, y, 2, cg.snap->ps.inventory[cg.inventorySelect], 6, 12, NUM_FONT_SMALL,qfalse);*/ itemNdex = BG_GetItemIndexByTag(cg.itemSelect, IT_HOLDABLE); if (bg_itemlist[itemNdex].classname) { vec4_t textColor = { .312f, .75f, .621f, 1.0f }; char text[1024]; if ( trap_SP_GetStringTextString( va("INGAME_%s",bg_itemlist[itemNdex].classname), text, sizeof( text ))) { UI_DrawProportionalString(320, y+45, text, UI_CENTER | UI_SMALLFONT, textColor); } else { UI_DrawProportionalString(320, y+45, bg_itemlist[itemNdex].classname, UI_CENTER | UI_SMALLFONT, textColor); } } } i = cg.itemSelect + 1; if (i> HI_NUM_HOLDABLE-1) { i = 0; } // Right side ICONS // Work forwards from current icon holdX = x + (bigIconSize/2) + pad; height = smallIconSize * cg.iconHUDPercent; addX = (float) smallIconSize * .75; for (iconCnt=0;iconCnt HI_NUM_HOLDABLE-1) { i = 0; } if ( !(cg.snap->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << i)) || i == cg.itemSelect ) { continue; } ++iconCnt; // Good icon if (cgs.media.invenIcons[i]) { trap_R_SetColor(NULL); CG_DrawPic( holdX, y+10, smallIconSize, smallIconSize, cgs.media.invenIcons[i] ); trap_R_SetColor(colorTable[CT_ICON_BLUE]); /*CG_DrawNumField (holdX + addX, y + smallIconSize, 2, cg.snap->ps.inventory[i], 6, 12, NUM_FONT_SMALL,qfalse);*/ holdX += (smallIconSize+pad); } } } /* ================ CG_DrawStats ================ */ static void CG_DrawStats( void ) { centity_t *cent; /* playerState_t *ps; vec3_t angles; // vec3_t origin; if ( cg_drawStatus.integer == 0 ) { return; } */ cent = &cg_entities[cg.snap->ps.clientNum]; /* ps = &cg.snap->ps; VectorClear( angles ); // Do start if (!cg.interfaceStartupDone) { CG_InterfaceStartup(); } cgi_UI_MenuPaintAll();*/ CG_DrawHUD(cent); /*CG_DrawArmor(cent); CG_DrawHealth(cent); CG_DrawAmmo(cent); CG_DrawTalk(cent);*/ } /* ================ CG_DrawTeamBackground ================ */ void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team ) { vec4_t hcolor; hcolor[3] = alpha; if ( team == TEAM_RED ) { hcolor[0] = 1; hcolor[1] = .2f; hcolor[2] = .2f; } else if ( team == TEAM_BLUE ) { hcolor[0] = .2f; hcolor[1] = .2f; hcolor[2] = 1; } else { return; } // trap_R_SetColor( hcolor ); CG_FillRect ( x, y, w, h, hcolor ); // CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); trap_R_SetColor( NULL ); } /* =========================================================================================== UPPER RIGHT CORNER =========================================================================================== */ /* ================ CG_DrawMiniScoreboard ================ */ static float CG_DrawMiniScoreboard ( float y ) { char temp[MAX_QPATH]; if ( !cg_drawScores.integer ) { return y; } if ( cgs.gametype >= GT_TEAM ) { strcpy ( temp, "Red: " ); Q_strcat ( temp, MAX_QPATH, cgs.scores1==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores1)) ); Q_strcat ( temp, MAX_QPATH, " Blue: " ); Q_strcat ( temp, MAX_QPATH, cgs.scores2==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores2)) ); CG_Text_Paint( 630 - CG_Text_Width ( temp, 0.7f, FONT_MEDIUM ), y, 0.7f, colorWhite, temp, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM ); y += 15; } else { /* strcpy ( temp, "1st: " ); Q_strcat ( temp, MAX_QPATH, cgs.scores1==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores1)) ); Q_strcat ( temp, MAX_QPATH, " 2nd: " ); Q_strcat ( temp, MAX_QPATH, cgs.scores2==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores2)) ); CG_Text_Paint( 630 - CG_Text_Width ( temp, 0.75f, FONT_SMALL ), y, 0.75f, colorWhite, temp, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM ); y += 15; */ //rww - no longer doing this. Since the attacker now shows who is first, we print the score there. } return y; } /* ================ CG_DrawEnemyInfo ================ */ static float CG_DrawEnemyInfo ( float y ) { float size; int clientNum; const char *title; clientInfo_t *ci; if ( !cg_drawEnemyInfo.integer ) { return y; } if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) { return y; } if ( cgs.gametype == GT_JEDIMASTER ) { title = "Jedi Master"; clientNum = cgs.jediMaster; if ( clientNum < 0 ) { //return y; title = "Get Saber!"; size = ICON_SIZE * 1.25; y += 5; CG_DrawPic( 640 - size - 12, y, size, size, cgs.media.weaponIcons[WP_SABER] ); y += size; /* CG_Text_Paint( 630 - CG_Text_Width ( ci->name, 0.75f, FONT_MEDIUM ), y, 0.75f, colorWhite, ci->name, 0, 0, 0, FONT_MEDIUM ); y += 15; */ CG_Text_Paint( 630 - CG_Text_Width ( title, 0.75f, FONT_MEDIUM ), y, 0.75f, colorWhite, title, 0, 0, 0, FONT_MEDIUM ); return y + BIGCHAR_HEIGHT + 2; } } else if ( cg.snap->ps.duelInProgress ) { title = "Dueling"; clientNum = cg.snap->ps.duelIndex; } else if ( cgs.gametype == GT_TOURNAMENT && cgs.clientinfo[cg.snap->ps.clientNum].team != TEAM_SPECTATOR) { title = "Dueling"; if (cg.snap->ps.clientNum == cgs.duelist1) { clientNum = cgs.duelist2; } else if (cg.snap->ps.clientNum == cgs.duelist2) { clientNum = cgs.duelist1; } else { return y; } } else { /* title = "Attacker"; clientNum = cg.predictedPlayerState.persistant[PERS_ATTACKER]; if ( clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg.snap->ps.clientNum ) { return y; } if ( cg.time - cg.attackerTime > ATTACKER_HEAD_TIME ) { cg.attackerTime = 0; return y; } */ //As of current, we don't want to draw the attacker. Instead, draw whoever is in first place. if (cgs.duelWinner < 0 || cgs.duelWinner >= MAX_CLIENTS) { return y; } title = va("%s: %i",CG_GetStripEdString("INGAMETEXT", "LEADER"), cgs.scores1); /* if (cgs.scores1 == 1) { title = va("%i kill", cgs.scores1); } else { title = va("%i kills", cgs.scores1); } */ clientNum = cgs.duelWinner; } ci = &cgs.clientinfo[ clientNum ]; if (!ci || !ci->modelIcon) { return y; } size = ICON_SIZE * 1.25; y += 5; CG_DrawPic( 640 - size - 5, y, size, size, ci->modelIcon ); y += size; CG_Text_Paint( 630 - CG_Text_Width ( ci->name, 0.75f, FONT_MEDIUM ), y, 0.75f, colorWhite, ci->name, 0, 0, 0, FONT_MEDIUM ); y += 15; CG_Text_Paint( 630 - CG_Text_Width ( title, 0.75f, FONT_MEDIUM ), y, 0.75f, colorWhite, title, 0, 0, 0, FONT_MEDIUM ); return y + BIGCHAR_HEIGHT + 2; } /* ================== CG_DrawSnapshot ================== */ static float CG_DrawSnapshot( float y ) { char *s; int w; s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime, cg.latestSnapshotNum, cgs.serverCommandSequence ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString( 635 - w, y + 2, s, 1.0F); return y + BIGCHAR_HEIGHT + 4; } /* ================== CG_DrawFPS ================== */ #define FPS_FRAMES 4 static float CG_DrawFPS( float y ) { char *s; int w; static int previousTimes[FPS_FRAMES]; static int index; int i, total; int fps; static int previous; int t, frameTime; // don't use serverTime, because that will be drifting to // correct for internet lag changes, timescales, timedemos, etc t = trap_Milliseconds(); frameTime = t - previous; previous = t; previousTimes[index % FPS_FRAMES] = frameTime; index++; if ( index > FPS_FRAMES ) { // average multiple frames together to smooth changes out a bit total = 0; for ( i = 0 ; i < FPS_FRAMES ; i++ ) { total += previousTimes[i]; } if ( !total ) { total = 1; } fps = 1000 * FPS_FRAMES / total; s = va( "%ifps", fps ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString( 635 - w, y + 2, s, 1.0F); } return y + BIGCHAR_HEIGHT + 4; } /* ================= CG_DrawTimer ================= */ static float CG_DrawTimer( float y ) { char *s; int w; int mins, seconds, tens; int msec; msec = cg.time - cgs.levelStartTime; seconds = msec / 1000; mins = seconds / 60; seconds -= mins * 60; tens = seconds / 10; seconds -= tens * 10; s = va( "%i:%i%i", mins, tens, seconds ); w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString( 635 - w, y + 2, s, 1.0F); return y + BIGCHAR_HEIGHT + 4; } /* ================= CG_DrawTeamOverlay ================= */ static float CG_DrawTeamOverlay( float y, qboolean right, qboolean upper ) { int x, w, h, xx; int i, j, len; const char *p; vec4_t hcolor; int pwidth, lwidth; int plyrs; char st[16]; clientInfo_t *ci; gitem_t *item; int ret_y, count; if ( !cg_drawTeamOverlay.integer ) { return y; } if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_RED && cg.snap->ps.persistant[PERS_TEAM] != TEAM_BLUE ) { return y; // Not on any team } plyrs = 0; // max player name width pwidth = 0; count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers; for (i = 0; i < count; i++) { ci = cgs.clientinfo + sortedTeamPlayers[i]; if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { plyrs++; len = CG_DrawStrlen(ci->name); if (len > pwidth) pwidth = len; } } if (!plyrs) return y; if (pwidth > TEAM_OVERLAY_MAXNAME_WIDTH) pwidth = TEAM_OVERLAY_MAXNAME_WIDTH; // max location name width lwidth = 0; for (i = 1; i < MAX_LOCATIONS; i++) { p = CG_ConfigString(CS_LOCATIONS + i); if (p && *p) { len = CG_DrawStrlen(p); if (len > lwidth) lwidth = len; } } if (lwidth > TEAM_OVERLAY_MAXLOCATION_WIDTH) lwidth = TEAM_OVERLAY_MAXLOCATION_WIDTH; w = (pwidth + lwidth + 4 + 7) * TINYCHAR_WIDTH; if ( right ) x = 640 - w; else x = 0; h = plyrs * TINYCHAR_HEIGHT; if ( upper ) { ret_y = y + h; } else { y -= h; ret_y = y; } if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) { hcolor[0] = 1.0f; hcolor[1] = 0.0f; hcolor[2] = 0.0f; hcolor[3] = 0.33f; } else { // if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE ) hcolor[0] = 0.0f; hcolor[1] = 0.0f; hcolor[2] = 1.0f; hcolor[3] = 0.33f; } trap_R_SetColor( hcolor ); CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); trap_R_SetColor( NULL ); for (i = 0; i < count; i++) { ci = cgs.clientinfo + sortedTeamPlayers[i]; if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) { hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0; xx = x + TINYCHAR_WIDTH; CG_DrawStringExt( xx, y, ci->name, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH); if (lwidth) { p = CG_ConfigString(CS_LOCATIONS + ci->location); if (!p || !*p) p = "unknown"; len = CG_DrawStrlen(p); if (len > lwidth) len = lwidth; // xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth + // ((lwidth/2 - len/2) * TINYCHAR_WIDTH); xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth; CG_DrawStringExt( xx, y, p, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXLOCATION_WIDTH); } CG_GetColorForHealth( ci->health, ci->armor, hcolor ); Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor); xx = x + TINYCHAR_WIDTH * 3 + TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth; CG_DrawStringExt( xx, y, st, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 ); // draw weapon icon xx += TINYCHAR_WIDTH * 3; if ( cg_weapons[ci->curWeapon].weaponIcon ) { CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, cg_weapons[ci->curWeapon].weaponIcon ); } else { CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, cgs.media.deferShader ); } // Draw powerup icons if (right) { xx = x; } else { xx = x + w - TINYCHAR_WIDTH; } for (j = 0; j <= PW_NUM_POWERUPS; j++) { if (ci->powerups & (1 << j)) { item = BG_FindItemForPowerup( j ); if (item) { CG_DrawPic( xx, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT, trap_R_RegisterShader( item->icon ) ); if (right) { xx -= TINYCHAR_WIDTH; } else { xx += TINYCHAR_WIDTH; } } } } y += TINYCHAR_HEIGHT; } } return ret_y; //#endif } static void CG_DrawPowerupIcons(int y) { int j; int ico_size = 64; //int y = ico_size/2; gitem_t *item; if (!cg.snap) { return; } y += 16; for (j = 0; j <= PW_NUM_POWERUPS; j++) { if (cg.snap->ps.powerups[j] > cg.time) { int secondsleft = (cg.snap->ps.powerups[j] - cg.time)/1000; item = BG_FindItemForPowerup( j ); if (item) { int icoShader = 0; if (cgs.gametype == GT_CTY && (j == PW_REDFLAG || j == PW_BLUEFLAG)) { if (j == PW_REDFLAG) { icoShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_ys" ); } else { icoShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_ys" ); } } else { icoShader = trap_R_RegisterShader( item->icon ); } CG_DrawPic( (640-(ico_size*1.1)), y, ico_size, ico_size, icoShader ); y += ico_size; if (j != PW_REDFLAG && j != PW_BLUEFLAG && secondsleft < 999) { UI_DrawProportionalString((640-(ico_size*1.1))+(ico_size/2), y-8, va("%i", secondsleft), UI_CENTER | UI_BIGFONT | UI_DROPSHADOW, colorTable[CT_WHITE]); } y += (ico_size/3); } } } } /* ===================== CG_DrawUpperRight ===================== */ static void CG_DrawUpperRight( void ) { float y; y = 0; if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 1 ) { y = CG_DrawTeamOverlay( y, qtrue, qtrue ); } if ( cg_drawSnapshot.integer ) { y = CG_DrawSnapshot( y ); } if ( cg_drawFPS.integer ) { y = CG_DrawFPS( y ); } if ( cg_drawTimer.integer ) { y = CG_DrawTimer( y ); } y = CG_DrawEnemyInfo ( y ); y = CG_DrawMiniScoreboard ( y ); CG_DrawPowerupIcons(y); } /* =================== CG_DrawReward =================== */ #ifdef JK2AWARDS static void CG_DrawReward( void ) { float *color; int i, count; float x, y; char buf[32]; if ( !cg_drawRewards.integer ) { return; } color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); if ( !color ) { if (cg.rewardStack > 0) { for(i = 0; i < cg.rewardStack; i++) { cg.rewardSound[i] = cg.rewardSound[i+1]; cg.rewardShader[i] = cg.rewardShader[i+1]; cg.rewardCount[i] = cg.rewardCount[i+1]; } cg.rewardTime = cg.time; cg.rewardStack--; color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); trap_S_StartLocalSound(cg.rewardSound[0], CHAN_ANNOUNCER); } else { return; } } trap_R_SetColor( color ); /* count = cg.rewardCount[0]/10; // number of big rewards to draw if (count) { y = 4; x = 320 - count * ICON_SIZE; for ( i = 0 ; i < count ; i++ ) { CG_DrawPic( x, y, (ICON_SIZE*2)-4, (ICON_SIZE*2)-4, cg.rewardShader[0] ); x += (ICON_SIZE*2); } } count = cg.rewardCount[0] - count*10; // number of small rewards to draw */ if ( cg.rewardCount[0] >= 10 ) { y = 56; x = 320 - ICON_SIZE/2; CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); Com_sprintf(buf, sizeof(buf), "%d", cg.rewardCount[0]); x = ( SCREEN_WIDTH - SMALLCHAR_WIDTH * CG_DrawStrlen( buf ) ) / 2; CG_DrawStringExt( x, y+ICON_SIZE, buf, color, qfalse, qtrue, SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 ); } else { count = cg.rewardCount[0]; y = 56; x = 320 - count * ICON_SIZE/2; for ( i = 0 ; i < count ; i++ ) { CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] ); x += ICON_SIZE; } } trap_R_SetColor( NULL ); } #endif /* =============================================================================== LAGOMETER =============================================================================== */ #define LAG_SAMPLES 128 typedef struct { int frameSamples[LAG_SAMPLES]; int frameCount; int snapshotFlags[LAG_SAMPLES]; int snapshotSamples[LAG_SAMPLES]; int snapshotCount; } lagometer_t; lagometer_t lagometer; /* ============== CG_AddLagometerFrameInfo Adds the current interpolate / extrapolate bar for this frame ============== */ void CG_AddLagometerFrameInfo( void ) { int offset; offset = cg.time - cg.latestSnapshotTime; lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1) ] = offset; lagometer.frameCount++; } /* ============== CG_AddLagometerSnapshotInfo Each time a snapshot is received, log its ping time and the number of snapshots that were dropped before it. Pass NULL for a dropped packet. ============== */ void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) { // dropped packet if ( !snap ) { lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = -1; lagometer.snapshotCount++; return; } // add this snapshot's info lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->ping; lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->snapFlags; lagometer.snapshotCount++; } /* ============== CG_DrawDisconnect Should we draw something differnet for long lag vs no packets? ============== */ static void CG_DrawDisconnect( void ) { float x, y; int cmdNum; usercmd_t cmd; const char *s; int w; // bk010215 - FIXME char message[1024]; if (cg.mMapChange) { s = CG_GetStripEdString("INGAMETEXT", "SERVER_CHANGING_MAPS"); // s = "Server Changing Maps"; w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString( 320 - w/2, 100, s, 1.0F); s = CG_GetStripEdString("INGAMETEXT", "PLEASE_WAIT"); // s = "Please wait..."; w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString( 320 - w/2, 200, s, 1.0F); return; } // draw the phone jack if we are completely past our buffers cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1; trap_GetUserCmd( cmdNum, &cmd ); if ( cmd.serverTime <= cg.snap->ps.commandTime || cmd.serverTime > cg.time ) { // special check for map_restart // bk 0102165 - FIXME return; } // also add text in center of screen s = CG_GetStripEdString("INGAMETEXT", "CONNECTION_INTERRUPTED"); // s = "Connection Interrupted"; // bk 010215 - FIXME w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString( 320 - w/2, 100, s, 1.0F); // blink the icon if ( ( cg.time >> 9 ) & 1 ) { return; } x = 640 - 48; y = 480 - 48; CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader("gfx/2d/net.tga" ) ); } #define MAX_LAGOMETER_PING 900 #define MAX_LAGOMETER_RANGE 300 /* ============== CG_DrawLagometer ============== */ static void CG_DrawLagometer( void ) { int a, x, y, i; float v; float ax, ay, aw, ah, mid, range; int color; float vscale; if ( !cg_lagometer.integer || cgs.localServer ) { CG_DrawDisconnect(); return; } // // draw the graph // x = 640 - 48; y = 480 - 144; trap_R_SetColor( NULL ); CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader ); ax = x; ay = y; aw = 48; ah = 48; color = -1; range = ah / 3; mid = ay + range; vscale = range / MAX_LAGOMETER_RANGE; // draw the frame interpoalte / extrapolate graph for ( a = 0 ; a < aw ; a++ ) { i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1); v = lagometer.frameSamples[i]; v *= vscale; if ( v > 0 ) { if ( color != 1 ) { color = 1; trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); } if ( v > range ) { v = range; } trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); } else if ( v < 0 ) { if ( color != 2 ) { color = 2; trap_R_SetColor( g_color_table[ColorIndex(COLOR_BLUE)] ); } v = -v; if ( v > range ) { v = range; } trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); } } // draw the snapshot latency / drop graph range = ah / 2; vscale = range / MAX_LAGOMETER_PING; for ( a = 0 ; a < aw ; a++ ) { i = ( lagometer.snapshotCount - 1 - a ) & (LAG_SAMPLES - 1); v = lagometer.snapshotSamples[i]; if ( v > 0 ) { if ( lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED ) { if ( color != 5 ) { color = 5; // YELLOW for rate delay trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] ); } } else { if ( color != 3 ) { color = 3; trap_R_SetColor( g_color_table[ColorIndex(COLOR_GREEN)] ); } } v = v * vscale; if ( v > range ) { v = range; } trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader ); } else if ( v < 0 ) { if ( color != 4 ) { color = 4; // RED for dropped snapshots trap_R_SetColor( g_color_table[ColorIndex(COLOR_RED)] ); } trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader ); } } trap_R_SetColor( NULL ); if ( cg_nopredict.integer || cg_synchronousClients.integer ) { CG_DrawBigString( ax, ay, "snc", 1.0 ); } CG_DrawDisconnect(); } /* =============================================================================== CENTER PRINTING =============================================================================== */ /* ============== CG_CenterPrint Called for important messages that should stay in the center of the screen for a few moments ============== */ void CG_CenterPrint( const char *str, int y, int charWidth ) { char *s; Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) ); cg.centerPrintTime = cg.time; cg.centerPrintY = y; cg.centerPrintCharWidth = charWidth; // count the number of lines for centering cg.centerPrintLines = 1; s = cg.centerPrint; while( *s ) { if (*s == '\n') cg.centerPrintLines++; s++; } } /* =================== CG_DrawCenterString =================== */ static void CG_DrawCenterString( void ) { char *start; int l; int x, y, w; int h; float *color; const float scale = 1.0; //0.5 if ( !cg.centerPrintTime ) { return; } color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value ); if ( !color ) { return; } trap_R_SetColor( color ); start = cg.centerPrint; y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2; while ( 1 ) { char linebuffer[1024]; for ( l = 0; l < 50; l++ ) { if ( !start[l] || start[l] == '\n' ) { break; } linebuffer[l] = start[l]; } linebuffer[l] = 0; w = CG_Text_Width(linebuffer, scale, FONT_MEDIUM); h = CG_Text_Height(linebuffer, scale, FONT_MEDIUM); x = (SCREEN_WIDTH - w) / 2; CG_Text_Paint(x, y + h, scale, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM); y += h + 6; while ( *start && ( *start != '\n' ) ) { start++; } if ( !*start ) { break; } start++; } trap_R_SetColor( NULL ); } /* ================================================================================ CROSSHAIR ================================================================================ */ /* ================= CG_DrawCrosshair ================= */ static void CG_DrawCrosshair( vec3_t worldPoint, int chEntValid ) { float w, h; qhandle_t hShader; float f; float x, y; if ( !cg_drawCrosshair.integer ) { return; } if (cg.snap->ps.fallingToDeath) { return; } if ( cg.predictedPlayerState.zoomMode != 0 ) {//not while scoped return; } if ( cg_crosshairHealth.integer ) { vec4_t hcolor; CG_ColorForHealth( hcolor ); trap_R_SetColor( hcolor ); } else { //set color based on what kind of ent is under crosshair if ( cg.crosshairClientNum >= ENTITYNUM_WORLD ) { trap_R_SetColor( NULL ); } else if (chEntValid && (cg_entities[cg.crosshairClientNum].currentState.number < MAX_CLIENTS || cg_entities[cg.crosshairClientNum].currentState.shouldtarget)) { vec4_t ecolor = {0,0,0,0}; centity_t *crossEnt = &cg_entities[cg.crosshairClientNum]; if ( crossEnt->currentState.number < MAX_CLIENTS ) { if (cgs.gametype >= GT_TEAM && cgs.clientinfo[crossEnt->currentState.number].team == cgs.clientinfo[cg.snap->ps.clientNum].team ) { //Allies are green ecolor[0] = 0.0;//R ecolor[1] = 1.0;//G ecolor[2] = 0.0;//B } else { //Enemies are red ecolor[0] = 1.0;//R ecolor[1] = 0.0;//G ecolor[2] = 0.0;//B } if (cg.snap->ps.duelInProgress) { if (crossEnt->currentState.number != cg.snap->ps.duelIndex) { //grey out crosshair for everyone but your foe if you're in a duel ecolor[0] = 0.4; ecolor[1] = 0.4; ecolor[2] = 0.4; } } else if (crossEnt->currentState.bolt1) { //this fellow is in a duel. We just checked if we were in a duel above, so //this means we aren't and he is. Which of course means our crosshair greys out over him. ecolor[0] = 0.4; ecolor[1] = 0.4; ecolor[2] = 0.4; } } else if (crossEnt->currentState.shouldtarget) { //VectorCopy( crossEnt->startRGBA, ecolor ); if ( !ecolor[0] && !ecolor[1] && !ecolor[2] ) { // We really don't want black, so set it to yellow ecolor[0] = 1.0F;//R ecolor[1] = 0.8F;//G ecolor[2] = 0.3F;//B } if (crossEnt->currentState.owner == cg.snap->ps.clientNum || (cgs.gametype >= GT_TEAM && crossEnt->currentState.teamowner == cgs.clientinfo[cg.snap->ps.clientNum].team)) { ecolor[0] = 0.0;//R ecolor[1] = 1.0;//G ecolor[2] = 0.0;//B } else if (crossEnt->currentState.teamowner == 16 || (cgs.gametype >= GT_TEAM && crossEnt->currentState.teamowner && crossEnt->currentState.teamowner != cgs.clientinfo[cg.snap->ps.clientNum].team)) { ecolor[0] = 1.0;//R ecolor[1] = 0.0;//G ecolor[2] = 0.0;//B } } ecolor[3] = 1.0; trap_R_SetColor( ecolor ); } } w = h = cg_crosshairSize.value; // pulse the size of the crosshair when picking up items f = cg.time - cg.itemPickupBlendTime; if ( f > 0 && f < ITEM_BLOB_TIME ) { f /= ITEM_BLOB_TIME; w *= ( 1 + f ); h *= ( 1 + f ); } if ( worldPoint && VectorLength( worldPoint ) ) { if ( !CG_WorldCoordToScreenCoordFloat( worldPoint, &x, &y ) ) {//off screen, don't draw it return; } x -= 320; y -= 240; } else { x = cg_crosshairX.integer; y = cg_crosshairY.integer; } hShader = cgs.media.crosshairShader[ cg_drawCrosshair.integer % NUM_CROSSHAIRS ]; trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (640 - w), y + cg.refdef.y + 0.5 * (480 - h), w, h, 0, 0, 1, 1, hShader ); } qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y) { int xcenter, ycenter; vec3_t local, transformed; vec3_t vfwd; vec3_t vright; vec3_t vup; float xzi; float yzi; // xcenter = cg.refdef.width / 2;//gives screen coords adjusted for resolution // ycenter = cg.refdef.height / 2;//gives screen coords adjusted for resolution //NOTE: did it this way because most draw functions expect virtual 640x480 coords // and adjust them for current resolution xcenter = 640 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn ycenter = 480 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn AngleVectors (cg.refdefViewAngles, vfwd, vright, vup); VectorSubtract (worldCoord, cg.refdef.vieworg, local); transformed[0] = DotProduct(local,vright); transformed[1] = DotProduct(local,vup); transformed[2] = DotProduct(local,vfwd); // Make sure Z is not negative. if(transformed[2] < 0.01) { return qfalse; } xzi = xcenter / transformed[2] * (90.0/cg.refdef.fov_x); yzi = ycenter / transformed[2] * (90.0/cg.refdef.fov_y); *x = xcenter + xzi * transformed[0]; *y = ycenter - yzi * transformed[1]; return qtrue; } qboolean CG_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y ) { float xF, yF; qboolean retVal = CG_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF ); *x = (int)xF; *y = (int)yF; return retVal; } /* ==================== CG_SaberClashFlare ==================== */ int g_saberFlashTime = 0; vec3_t g_saberFlashPos = {0, 0, 0}; void CG_SaberClashFlare( void ) { int t, maxTime = 150; vec3_t dif; vec3_t color; int x,y; float v, len; trace_t tr; t = cg.time - g_saberFlashTime; if ( t <= 0 || t >= maxTime ) { return; } // Don't do clashes for things that are behind us VectorSubtract( g_saberFlashPos, cg.refdef.vieworg, dif ); if ( DotProduct( dif, cg.refdef.viewaxis[0] ) < 0.2 ) { return; } CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, g_saberFlashPos, -1, CONTENTS_SOLID ); if ( tr.fraction < 1.0f ) { return; } len = VectorNormalize( dif ); // clamp to a known range if ( len > 800 ) { len = 800; } v = ( 1.0f - ((float)t / maxTime )) * ((1.0f - ( len / 800.0f )) * 2.0f + 0.35f); CG_WorldCoordToScreenCoord( g_saberFlashPos, &x, &y ); VectorSet( color, 0.8f, 0.8f, 0.8f ); trap_R_SetColor( color ); CG_DrawPic( x - ( v * 300 ), y - ( v * 300 ), v * 600, v * 600, trap_R_RegisterShader( "gfx/effects/saberFlare" )); } //-------------------------------------------------------------- static void CG_DrawHolocronIcons(void) //-------------------------------------------------------------- { int icon_size = 40; int i = 0; int startx = 10; int starty = 10;//SCREEN_HEIGHT - icon_size*3; int endx = icon_size; int endy = icon_size; if (cg.snap->ps.zoomMode) { //don't display over zoom mask return; } if (cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR) { return; } while (i < NUM_FORCE_POWERS) { if (cg.snap->ps.holocronBits & (1 << forcePowerSorted[i])) { CG_DrawPic( startx, starty, endx, endy, cgs.media.forcePowerIcons[forcePowerSorted[i]]); starty += (icon_size+2); //+2 for spacing if ((starty+icon_size) >= SCREEN_HEIGHT-80) { starty = 10;//SCREEN_HEIGHT - icon_size*3; startx += (icon_size+2); } } i++; } } static qboolean CG_IsDurationPower(int power) { if (power == FP_HEAL || power == FP_SPEED || power == FP_TELEPATHY || power == FP_RAGE || power == FP_PROTECT || power == FP_ABSORB || power == FP_SEE) { return qtrue; } return qfalse; } //-------------------------------------------------------------- static void CG_DrawActivePowers(void) //-------------------------------------------------------------- { int icon_size = 40; int i = 0; int startx = icon_size*2+16; int starty = SCREEN_HEIGHT - icon_size*2; int endx = icon_size; int endy = icon_size; if (cg.snap->ps.zoomMode) { //don't display over zoom mask return; } if (cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR) { return; } while (i < NUM_FORCE_POWERS) { if ((cg.snap->ps.fd.forcePowersActive & (1 << forcePowerSorted[i])) && CG_IsDurationPower(forcePowerSorted[i])) { CG_DrawPic( startx, starty, endx, endy, cgs.media.forcePowerIcons[forcePowerSorted[i]]); startx += (icon_size+2); //+2 for spacing if ((startx+icon_size) >= SCREEN_WIDTH-80) { startx = icon_size*2+16; starty += (icon_size+2); } } i++; } //additionally, draw an icon force force rage recovery if (cg.snap->ps.fd.forceRageRecoveryTime > cg.time) { CG_DrawPic( startx, starty, endx, endy, cgs.media.rageRecShader); } } //-------------------------------------------------------------- static void CG_DrawRocketLocking( int lockEntNum, int lockTime ) //-------------------------------------------------------------- { int cx, cy; vec3_t org; static int oldDif = 0; centity_t *cent = &cg_entities[lockEntNum]; vec4_t color={0.0f,0.0f,0.0f,0.0f}; int dif = ( cg.time - cg.snap->ps.rocketLockTime ) / ( 1200.0f / /*8.0f*/16.0f ); int i; if (!cg.snap->ps.rocketLockTime) { return; } if (cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR) { return; } //We can't check to see in pmove if players are on the same team, so we resort //to just not drawing the lock if a teammate is the locked on ent if (cg.snap->ps.rocketLockIndex >= 0 && cg.snap->ps.rocketLockIndex < MAX_CLIENTS) { if (cgs.clientinfo[cg.snap->ps.rocketLockIndex].team == cgs.clientinfo[cg.snap->ps.clientNum].team) { if (cgs.gametype >= GT_TEAM) { return; } } } if (cg.snap->ps.rocketLockTime != -1) { lastvalidlockdif = dif; } else { dif = lastvalidlockdif; } if ( !cent ) { return; } VectorCopy( cent->lerpOrigin, org ); if ( CG_WorldCoordToScreenCoord( org, &cx, &cy )) { // we care about distance from enemy to eye, so this is good enough float sz = Distance( cent->lerpOrigin, cg.refdef.vieworg ) / 1024.0f; if ( sz > 1.0f ) { sz = 1.0f; } else if ( sz < 0.0f ) { sz = 0.0f; } sz = (1.0f - sz) * (1.0f - sz) * 32 + 6; cy += sz * 0.5f; if ( dif < 0 ) { oldDif = 0; return; } else if ( dif > 8 ) { dif = 8; } // do sounds if ( oldDif != dif ) { if ( dif == 8 ) { trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/rocket/lock.wav" )); } else { trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/rocket/tick.wav" )); } } oldDif = dif; for ( i = 0; i < dif; i++ ) { color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f; color[3] = 0.1f * i + 0.2f; trap_R_SetColor( color ); // our slices are offset by about 45 degrees. CG_DrawRotatePic( cx - sz, cy - sz, sz, sz, i * 45.0f, trap_R_RegisterShaderNoMip( "gfx/2d/wedge" )); } // we are locked and loaded baby if ( dif == 8 ) { color[0] = color[1] = color[2] = sin( cg.time * 0.05f ) * 0.5f + 0.5f; color[3] = 1.0f; // this art is additive, so the alpha value does nothing trap_R_SetColor( color ); CG_DrawPic( cx - sz, cy - sz * 2, sz * 2, sz * 2, trap_R_RegisterShaderNoMip( "gfx/2d/lock" )); } } } /* ================= CG_ScanForCrosshairEntity ================= */ static void CG_ScanForCrosshairEntity( void ) { trace_t trace; vec3_t start, end; int content; if ( cg_dynamicCrosshair.integer ) { vec3_t d_f, d_rt, d_up; /* if ( cg.snap->ps.weapon == WP_NONE || cg.snap->ps.weapon == WP_SABER || cg.snap->ps.weapon == WP_STUN_BATON) { VectorCopy( cg.refdef.vieworg, start ); AngleVectors( cg.refdefViewAngles, d_f, d_rt, d_up ); } else */ //For now we still want to draw the crosshair in relation to the player's world coordinates //even if we have a melee weapon/no weapon. { if (cg.snap && cg.snap->ps.weapon == WP_EMPLACED_GUN && cg.snap->ps.emplacedIndex) { vec3_t pitchConstraint; VectorCopy(cg.refdefViewAngles, pitchConstraint); if (cg.renderingThirdPerson) { VectorCopy(cg.predictedPlayerState.viewangles, pitchConstraint); } else { VectorCopy(cg.refdefViewAngles, pitchConstraint); } if (pitchConstraint[PITCH] > 40) { pitchConstraint[PITCH] = 40; } AngleVectors( pitchConstraint, d_f, d_rt, d_up ); } else { vec3_t pitchConstraint; if (cg.renderingThirdPerson) { VectorCopy(cg.predictedPlayerState.viewangles, pitchConstraint); } else { VectorCopy(cg.refdefViewAngles, pitchConstraint); } AngleVectors( pitchConstraint, d_f, d_rt, d_up ); } CG_CalcMuzzlePoint(cg.snap->ps.clientNum, start); } //FIXME: increase this? Increase when zoom in? VectorMA( start, 4096, d_f, end );//was 8192 } else { VectorCopy( cg.refdef.vieworg, start ); VectorMA( start, 131072, cg.refdef.viewaxis[0], end ); } CG_Trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, CONTENTS_SOLID|CONTENTS_BODY ); if (trace.entityNum < MAX_CLIENTS) { if (CG_IsMindTricked(cg_entities[trace.entityNum].currentState.trickedentindex, cg_entities[trace.entityNum].currentState.trickedentindex2, cg_entities[trace.entityNum].currentState.trickedentindex3, cg_entities[trace.entityNum].currentState.trickedentindex4, cg.snap->ps.clientNum)) { if (cg.crosshairClientNum == trace.entityNum) { cg.crosshairClientNum = ENTITYNUM_NONE; cg.crosshairClientTime = 0; } CG_DrawCrosshair(trace.endpos, 0); return; //this entity is mind-tricking the current client, so don't render it } } if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR) { if (trace.entityNum < /*MAX_CLIENTS*/ENTITYNUM_WORLD) { CG_DrawCrosshair(trace.endpos, 1); } else { CG_DrawCrosshair(trace.endpos, 0); } } // if ( trace.entityNum >= MAX_CLIENTS ) { // return; // } // if the player is in fog, don't show it content = trap_CM_PointContents( trace.endpos, 0 ); if ( content & CONTENTS_FOG ) { return; } if ( trace.entityNum >= MAX_CLIENTS ) { cg.crosshairClientNum = trace.entityNum; cg.crosshairClientTime = cg.time; return; } // update the fade timer cg.crosshairClientNum = trace.entityNum; cg.crosshairClientTime = cg.time; } /* ===================== CG_DrawCrosshairNames ===================== */ static void CG_DrawCrosshairNames( void ) { float *color; vec4_t tcolor; char *name; int baseColor; if ( !cg_drawCrosshair.integer ) { return; } // scan the known entities to see if the crosshair is sighted on one CG_ScanForCrosshairEntity(); if ( !cg_drawCrosshairNames.integer ) { return; } //rww - still do the trace, our dynamic crosshair depends on it if (cg.crosshairClientNum >= MAX_CLIENTS) { return; } // draw the name of the player being looked at color = CG_FadeColor( cg.crosshairClientTime, 1000 ); if ( !color ) { trap_R_SetColor( NULL ); return; } name = cgs.clientinfo[ cg.crosshairClientNum ].name; if (cgs.gametype >= GT_TEAM) { if (cgs.clientinfo[cg.crosshairClientNum].team == TEAM_RED) { baseColor = CT_RED; } else { baseColor = CT_BLUE; } /* //For now instead of team-based we'll make it oriented based on which team we're on if (cgs.clientinfo[cg.crosshairClientNum].team == cgs.clientinfo[cg.snap->ps.clientNum].team) { baseColor = CT_GREEN; } else { baseColor = CT_RED; } */ } else { //baseColor = CT_WHITE; baseColor = CT_RED; //just make it red in nonteam modes since everyone is hostile and crosshair will be red on them too } if (cg.snap->ps.duelInProgress) { if (cg.crosshairClientNum != cg.snap->ps.duelIndex) { //grey out crosshair for everyone but your foe if you're in a duel baseColor = CT_BLACK; } } else if (cg_entities[cg.crosshairClientNum].currentState.bolt1) { //this fellow is in a duel. We just checked if we were in a duel above, so //this means we aren't and he is. Which of course means our crosshair greys out over him. baseColor = CT_BLACK; } tcolor[0] = colorTable[baseColor][0]; tcolor[1] = colorTable[baseColor][1]; tcolor[2] = colorTable[baseColor][2]; tcolor[3] = color[3]*0.5f; UI_DrawProportionalString(320, 170, name, UI_CENTER, tcolor); trap_R_SetColor( NULL ); } //============================================================================== /* ================= CG_DrawSpectator ================= */ static void CG_DrawSpectator(void) { const char* s; s = "SPECTATOR"; if (cgs.gametype == GT_TOURNAMENT && cgs.duelist1 != -1 && cgs.duelist2 != -1) { char text[1024]; int size = 64; Com_sprintf(text, sizeof(text), "%s %s %s", cgs.clientinfo[cgs.duelist1].name, CG_GetStripEdString("INGAMETEXT", "SPECHUD_VERSUS"), cgs.clientinfo[cgs.duelist2].name); CG_Text_Paint ( 320 - CG_Text_Width ( text, 1.0f, 3 ) / 2, 420, 1.0f, colorWhite, text, 0, 0, 0, 3 ); if (cgs.clientinfo[cgs.duelist1].modelIcon && cgs.clientinfo[cgs.duelist2].modelIcon) { trap_R_SetColor( colorTable[CT_WHITE] ); CG_DrawPic( 10, SCREEN_HEIGHT-(size*1.5), size, size, cgs.clientinfo[cgs.duelist1].modelIcon ); CG_DrawPic( SCREEN_WIDTH-size-10, SCREEN_HEIGHT-(size*1.5), size, size, cgs.clientinfo[cgs.duelist2].modelIcon ); } } else { CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 420, 1.0f, colorWhite, s, 0, 0, 0, 3 ); } if ( cgs.gametype == GT_TOURNAMENT ) { s = "waiting to play"; CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 440, 1.0f, colorWhite, s, 0, 0, 0, 3 ); } else //if ( cgs.gametype >= GT_TEAM ) { //s = "press ESC and use the JOIN menu to play"; s = CG_GetStripEdString("INGAMETEXT", "SPEC_CHOOSEJOIN"); CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 440, 1.0f, colorWhite, s, 0, 0, 0, 3 ); } } /* ================= CG_DrawVote ================= */ static void CG_DrawVote(void) { const char *s; int sec; char sYes[20]; char sNo[20]; if ( !cgs.voteTime ) { return; } // play a talk beep whenever it is modified if ( cgs.voteModified ) { cgs.voteModified = qfalse; // trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); } sec = ( VOTE_TIME - ( cg.time - cgs.voteTime ) ) / 1000; if ( sec < 0 ) { sec = 0; } trap_SP_GetStringTextString("MENUS0_YES", sYes, sizeof(sYes) ); trap_SP_GetStringTextString("MENUS0_NO", sNo, sizeof(sNo) ); s = va("VOTE(%i):%s %s:%i %s:%i", sec, cgs.voteString, sYes, cgs.voteYes, sNo, cgs.voteNo); CG_DrawSmallString( 4, 58, s, 1.0F ); s = CG_GetStripEdString("INGAMETEXT", "OR_PRESS_ESC_THEN_CLICK_VOTE"); // s = "or press ESC then click Vote"; CG_DrawSmallString( 4, 58 + SMALLCHAR_HEIGHT + 2, s, 1.0F ); } /* ================= CG_DrawTeamVote ================= */ static void CG_DrawTeamVote(void) { char *s; int sec, cs_offset; if ( cgs.clientinfo->team == TEAM_RED ) cs_offset = 0; else if ( cgs.clientinfo->team == TEAM_BLUE ) cs_offset = 1; else return; if ( !cgs.teamVoteTime[cs_offset] ) { return; } // play a talk beep whenever it is modified if ( cgs.teamVoteModified[cs_offset] ) { cgs.teamVoteModified[cs_offset] = qfalse; // trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND ); } sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[cs_offset] ) ) / 1000; if ( sec < 0 ) { sec = 0; } if (strstr(cgs.teamVoteString[cs_offset], "leader")) { int i = 0; while (cgs.teamVoteString[cs_offset][i] && cgs.teamVoteString[cs_offset][i] != ' ') { i++; } if (cgs.teamVoteString[cs_offset][i] == ' ') { int voteIndex = 0; char voteIndexStr[256]; i++; while (cgs.teamVoteString[cs_offset][i]) { voteIndexStr[voteIndex] = cgs.teamVoteString[cs_offset][i]; voteIndex++; i++; } voteIndexStr[voteIndex] = 0; voteIndex = atoi(voteIndexStr); s = va("TEAMVOTE(%i):(Make %s the new team leader) yes:%i no:%i", sec, cgs.clientinfo[voteIndex].name, cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); } else { s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset], cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); } } else { s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset], cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] ); } CG_DrawSmallString( 4, 90, s, 1.0F ); } static qboolean CG_DrawScoreboard() { return CG_DrawOldScoreboard(); #if 0 static qboolean firstTime = qtrue; float fade, *fadeColor; if (menuScoreboard) { menuScoreboard->window.flags &= ~WINDOW_FORCED; } if (cg_paused.integer) { cg.deferredPlayerLoading = 0; firstTime = qtrue; return qfalse; } // should never happen in Team Arena if (cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { cg.deferredPlayerLoading = 0; firstTime = qtrue; return qfalse; } // don't draw scoreboard during death while warmup up if ( cg.warmup && !cg.showScores ) { return qfalse; } if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || cg.predictedPlayerState.pm_type == PM_INTERMISSION ) { fade = 1.0; fadeColor = colorWhite; } else { fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME ); if ( !fadeColor ) { // next time scoreboard comes up, don't print killer cg.deferredPlayerLoading = 0; cg.killerName[0] = 0; firstTime = qtrue; return qfalse; } fade = *fadeColor; } if (menuScoreboard == NULL) { if ( cgs.gametype >= GT_TEAM ) { menuScoreboard = Menus_FindByName("teamscore_menu"); } else { menuScoreboard = Menus_FindByName("score_menu"); } } if (menuScoreboard) { if (firstTime) { CG_SetScoreSelection(menuScoreboard); firstTime = qfalse; } Menu_Paint(menuScoreboard, qtrue); } // load any models that have been deferred if ( ++cg.deferredPlayerLoading > 10 ) { CG_LoadDeferredPlayers(); } return qtrue; #endif } /* ================= CG_DrawIntermission ================= */ static void CG_DrawIntermission( void ) { // int key; //if (cg_singlePlayer.integer) { // CG_DrawCenterString(); // return; //} cg.scoreFadeTime = cg.time; cg.scoreBoardShowing = CG_DrawScoreboard(); } /* ================= CG_DrawFollow ================= */ static qboolean CG_DrawFollow( void ) { const char *s; if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) ) { return qfalse; } s = "following"; CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, 60, 1.0f, colorWhite, s, 0, 0, 0, FONT_MEDIUM ); s = cgs.clientinfo[ cg.snap->ps.clientNum ].name; CG_Text_Paint ( 320 - CG_Text_Width ( s, 2.0f, FONT_MEDIUM ) / 2, 80, 2.0f, colorWhite, s, 0, 0, 0, FONT_MEDIUM ); return qtrue; } #if 0 static void CG_DrawTemporaryStats() { //placeholder for testing (draws ammo and force power) char s[512]; if (!cg.snap) { return; } sprintf(s, "Force: %i", cg.snap->ps.fd.forcePower); CG_DrawBigString(SCREEN_WIDTH-164, SCREEN_HEIGHT-128, s, 1.0f); sprintf(s, "Ammo: %i", cg.snap->ps.ammo[weaponData[cg.snap->ps.weapon].ammoIndex]); CG_DrawBigString(SCREEN_WIDTH-164, SCREEN_HEIGHT-112, s, 1.0f); sprintf(s, "Health: %i", cg.snap->ps.stats[STAT_HEALTH]); CG_DrawBigString(8, SCREEN_HEIGHT-128, s, 1.0f); sprintf(s, "Armor: %i", cg.snap->ps.stats[STAT_ARMOR]); CG_DrawBigString(8, SCREEN_HEIGHT-112, s, 1.0f); } #endif /* ================= CG_DrawAmmoWarning ================= */ static void CG_DrawAmmoWarning( void ) { #if 0 const char *s; int w; if (!cg_drawStatus.integer) { return; } if ( cg_drawAmmoWarning.integer == 0 ) { return; } if ( !cg.lowAmmoWarning ) { return; } if ( cg.lowAmmoWarning == 2 ) { s = "OUT OF AMMO"; } else { s = "LOW AMMO WARNING"; } w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString(320 - w / 2, 64, s, 1.0F); #endif } /* ================= CG_DrawWarmup ================= */ static void CG_DrawWarmup( void ) { int w; int sec; int i; float scale; clientInfo_t *ci1, *ci2; int cw; const char *s; sec = cg.warmup; if ( !sec ) { return; } if ( sec < 0 ) { s = "Waiting for players"; w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH; CG_DrawBigString(320 - w / 2, 24, s, 1.0F); cg.warmupCount = 0; return; } if (cgs.gametype == GT_TOURNAMENT) { // find the two active players ci1 = NULL; ci2 = NULL; for ( i = 0 ; i < cgs.maxclients ; i++ ) { if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_FREE ) { if ( !ci1 ) { ci1 = &cgs.clientinfo[i]; } else { ci2 = &cgs.clientinfo[i]; } } } if ( ci1 && ci2 ) { s = va( "%s vs %s", ci1->name, ci2->name ); w = CG_Text_Width(s, 0.6f, FONT_MEDIUM); CG_Text_Paint(320 - w / 2, 60, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE,FONT_MEDIUM); } } else { if ( cgs.gametype == GT_FFA ) { s = "Free For All"; } else if ( cgs.gametype == GT_HOLOCRON ) { s = "Holocron FFA"; } else if ( cgs.gametype == GT_JEDIMASTER ) { s = "Jedi Master"; } else if ( cgs.gametype == GT_TEAM ) { s = "Team FFA"; } else if ( cgs.gametype == GT_SAGA ) { s = "N/A"; } else if ( cgs.gametype == GT_CTF ) { s = "Capture the Flag"; } else if ( cgs.gametype == GT_CTY ) { s = "Capture the Ysalamiri"; } else { s = ""; } w = CG_Text_Width(s, 1.5f, FONT_MEDIUM); CG_Text_Paint(320 - w / 2, 90, 1.5f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE,FONT_MEDIUM); } sec = ( sec - cg.time ) / 1000; if ( sec < 0 ) { cg.warmup = 0; sec = 0; } s = va( "Starts in: %i", sec + 1 ); if ( sec != cg.warmupCount ) { cg.warmupCount = sec; switch ( sec ) { case 0: trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER ); break; case 1: trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER ); break; case 2: trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER ); break; default: break; } } scale = 0.45f; switch ( cg.warmupCount ) { case 0: cw = 28; scale = 1.25f; break; case 1: cw = 24; scale = 1.15f; break; case 2: cw = 20; scale = 1.05f; break; default: cw = 16; scale = 0.9f; break; } w = CG_Text_Width(s, scale, FONT_MEDIUM); CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM); } //================================================================================== /* ================= CG_DrawTimedMenus ================= */ void CG_DrawTimedMenus() { if (cg.voiceTime) { int t = cg.time - cg.voiceTime; if ( t > 2500 ) { Menus_CloseByName("voiceMenu"); trap_Cvar_Set("cl_conXOffset", "0"); cg.voiceTime = 0; } } } void CG_DrawFlagStatus() { int myFlagTakenShader = 0; int theirFlagShader = 0; int team = 0; int startDrawPos = 2; int ico_size = 32; if (!cg.snap) { return; } if (cgs.gametype != GT_CTF && cgs.gametype != GT_CTY) { return; } team = cg.snap->ps.persistant[PERS_TEAM]; if (cgs.gametype == GT_CTY) { if (team == TEAM_RED) { myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_x" ); theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_ys" ); } else { myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_x" ); theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_ys" ); } } else { if (team == TEAM_RED) { myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_x" ); theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag" ); } else { myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_x" ); theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag" ); } } if (CG_YourTeamHasFlag()) { CG_DrawPic( startDrawPos, 365, ico_size, ico_size, theirFlagShader ); startDrawPos += ico_size+2; } if (CG_OtherTeamHasFlag()) { CG_DrawPic( startDrawPos, 365, ico_size, ico_size, myFlagTakenShader ); } } int cgRageTime = 0; int cgRageFadeTime = 0; float cgRageFadeVal = 0; int cgRageRecTime = 0; int cgRageRecFadeTime = 0; float cgRageRecFadeVal = 0; int cgAbsorbTime = 0; int cgAbsorbFadeTime = 0; float cgAbsorbFadeVal = 0; int cgProtectTime = 0; int cgProtectFadeTime = 0; float cgProtectFadeVal = 0; int cgYsalTime = 0; int cgYsalFadeTime = 0; float cgYsalFadeVal = 0; qboolean gCGHasFallVector = qfalse; vec3_t gCGFallVector; /* ================= CG_Draw2D ================= */ static void CG_Draw2D( void ) { float inTime = cg.invenSelectTime+WEAPON_SELECT_TIME; float wpTime = cg.weaponSelectTime+WEAPON_SELECT_TIME; float bestTime; int drawSelect = 0; float fallTime, rageTime, rageRecTime, absorbTime, protectTime, ysalTime; vec4_t hcolor; if (cgs.orderPending && cg.time > cgs.orderTime) { CG_CheckOrderPending(); } // if we are taking a levelshot for the menu, don't draw anything if ( cg.levelShot ) { return; } if (cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR) { cgRageTime = 0; cgRageFadeTime = 0; cgRageFadeVal = 0; cgRageRecTime = 0; cgRageRecFadeTime = 0; cgRageRecFadeVal = 0; cgAbsorbTime = 0; cgAbsorbFadeTime = 0; cgAbsorbFadeVal = 0; cgProtectTime = 0; cgProtectFadeTime = 0; cgProtectFadeVal = 0; cgYsalTime = 0; cgYsalFadeTime = 0; cgYsalFadeVal = 0; } if ( cg_draw2D.integer == 0 ) { return; } if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { CG_DrawIntermission(); return; } if (cgs.clientinfo[cg.snap->ps.clientNum].team != TEAM_SPECTATOR) { if (cg.snap->ps.fd.forcePowersActive & (1 << FP_RAGE)) { if (!cgRageTime) { cgRageTime = cg.time; } rageTime = (float)(cg.time - cgRageTime); rageTime /= 9000; if (rageTime < 0) { rageTime = 0; } if (rageTime > 0.15) { rageTime = 0.15; } hcolor[3] = rageTime; hcolor[0] = 0.7; hcolor[1] = 0; hcolor[2] = 0; if (!cg.renderingThirdPerson) { CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); } cgRageFadeTime = 0; cgRageFadeVal = 0; } else if (cgRageTime) { if (!cgRageFadeTime) { cgRageFadeTime = cg.time; cgRageFadeVal = 0.15; } rageTime = cgRageFadeVal; cgRageFadeVal -= (cg.time - cgRageFadeTime)*0.000005; if (rageTime < 0) { rageTime = 0; } if (rageTime > 0.15) { rageTime = 0.15; } if (cg.snap->ps.fd.forceRageRecoveryTime > cg.time) { float checkRageRecTime = rageTime; if (checkRageRecTime < 0.15) { checkRageRecTime = 0.15; } hcolor[3] = checkRageRecTime; hcolor[0] = rageTime*4; if (hcolor[0] < 0.2) { hcolor[0] = 0.2; } hcolor[1] = 0.2; hcolor[2] = 0.2; } else { hcolor[3] = rageTime; hcolor[0] = 0.7; hcolor[1] = 0; hcolor[2] = 0; } if (!cg.renderingThirdPerson && rageTime) { CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); } else { if (cg.snap->ps.fd.forceRageRecoveryTime > cg.time) { hcolor[3] = 0.15; hcolor[0] = 0.2; hcolor[1] = 0.2; hcolor[2] = 0.2; CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); } cgRageTime = 0; } } else if (cg.snap->ps.fd.forceRageRecoveryTime > cg.time) { if (!cgRageRecTime) { cgRageRecTime = cg.time; } rageRecTime = (float)(cg.time - cgRageRecTime); rageRecTime /= 9000; if (rageRecTime < 0.15)//0) { rageRecTime = 0.15;//0; } if (rageRecTime > 0.15) { rageRecTime = 0.15; } hcolor[3] = rageRecTime; hcolor[0] = 0.2; hcolor[1] = 0.2; hcolor[2] = 0.2; if (!cg.renderingThirdPerson) { CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); } cgRageRecFadeTime = 0; cgRageRecFadeVal = 0; } else if (cgRageRecTime) { if (!cgRageRecFadeTime) { cgRageRecFadeTime = cg.time; cgRageRecFadeVal = 0.15; } rageRecTime = cgRageRecFadeVal; cgRageRecFadeVal -= (cg.time - cgRageRecFadeTime)*0.000005; if (rageRecTime < 0) { rageRecTime = 0; } if (rageRecTime > 0.15) { rageRecTime = 0.15; } hcolor[3] = rageRecTime; hcolor[0] = 0.2; hcolor[1] = 0.2; hcolor[2] = 0.2; if (!cg.renderingThirdPerson && rageRecTime) { CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); } else { cgRageRecTime = 0; } } if (cg.snap->ps.fd.forcePowersActive & (1 << FP_ABSORB)) { if (!cgAbsorbTime) { cgAbsorbTime = cg.time; } absorbTime = (float)(cg.time - cgAbsorbTime); absorbTime /= 9000; if (absorbTime < 0) { absorbTime = 0; } if (absorbTime > 0.15) { absorbTime = 0.15; } hcolor[3] = absorbTime/2; hcolor[0] = 0; hcolor[1] = 0; hcolor[2] = 0.7; if (!cg.renderingThirdPerson) { CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); } cgAbsorbFadeTime = 0; cgAbsorbFadeVal = 0; } else if (cgAbsorbTime) { if (!cgAbsorbFadeTime) { cgAbsorbFadeTime = cg.time; cgAbsorbFadeVal = 0.15; } absorbTime = cgAbsorbFadeVal; cgAbsorbFadeVal -= (cg.time - cgAbsorbFadeTime)*0.000005; if (absorbTime < 0) { absorbTime = 0; } if (absorbTime > 0.15) { absorbTime = 0.15; } hcolor[3] = absorbTime/2; hcolor[0] = 0; hcolor[1] = 0; hcolor[2] = 0.7; if (!cg.renderingThirdPerson && absorbTime) { CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); } else { cgAbsorbTime = 0; } } if (cg.snap->ps.fd.forcePowersActive & (1 << FP_PROTECT)) { if (!cgProtectTime) { cgProtectTime = cg.time; } protectTime = (float)(cg.time - cgProtectTime); protectTime /= 9000; if (protectTime < 0) { protectTime = 0; } if (protectTime > 0.15) { protectTime = 0.15; } hcolor[3] = protectTime/2; hcolor[0] = 0; hcolor[1] = 0.7; hcolor[2] = 0; if (!cg.renderingThirdPerson) { CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); } cgProtectFadeTime = 0; cgProtectFadeVal = 0; } else if (cgProtectTime) { if (!cgProtectFadeTime) { cgProtectFadeTime = cg.time; cgProtectFadeVal = 0.15; } protectTime = cgProtectFadeVal; cgProtectFadeVal -= (cg.time - cgProtectFadeTime)*0.000005; if (protectTime < 0) { protectTime = 0; } if (protectTime > 0.15) { protectTime = 0.15; } hcolor[3] = protectTime/2; hcolor[0] = 0; hcolor[1] = 0.7; hcolor[2] = 0; if (!cg.renderingThirdPerson && protectTime) { CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); } else { cgProtectTime = 0; } } if (cg.snap->ps.rocketLockIndex != MAX_CLIENTS && (cg.time - cg.snap->ps.rocketLockTime) > 0) { CG_DrawRocketLocking( cg.snap->ps.rocketLockIndex, cg.snap->ps.rocketLockTime ); } if (BG_HasYsalamiri(cgs.gametype, &cg.snap->ps)) { if (!cgYsalTime) { cgYsalTime = cg.time; } ysalTime = (float)(cg.time - cgYsalTime); ysalTime /= 9000; if (ysalTime < 0) { ysalTime = 0; } if (ysalTime > 0.15) { ysalTime = 0.15; } hcolor[3] = ysalTime/2; hcolor[0] = 0.7; hcolor[1] = 0.7; hcolor[2] = 0; if (!cg.renderingThirdPerson) { CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); } cgYsalFadeTime = 0; cgYsalFadeVal = 0; } else if (cgYsalTime) { if (!cgYsalFadeTime) { cgYsalFadeTime = cg.time; cgYsalFadeVal = 0.15; } ysalTime = cgYsalFadeVal; cgYsalFadeVal -= (cg.time - cgYsalFadeTime)*0.000005; if (ysalTime < 0) { ysalTime = 0; } if (ysalTime > 0.15) { ysalTime = 0.15; } hcolor[3] = ysalTime/2; hcolor[0] = 0.7; hcolor[1] = 0.7; hcolor[2] = 0; if (!cg.renderingThirdPerson && ysalTime) { CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); } else { cgYsalTime = 0; } } } if (cg.snap->ps.rocketLockIndex != MAX_CLIENTS && (cg.time - cg.snap->ps.rocketLockTime) > 0) { CG_DrawRocketLocking( cg.snap->ps.rocketLockIndex, cg.snap->ps.rocketLockTime ); } if (cg.snap->ps.holocronBits) { CG_DrawHolocronIcons(); } if (cg.snap->ps.fd.forcePowersActive || cg.snap->ps.fd.forceRageRecoveryTime > cg.time) { CG_DrawActivePowers(); } // Draw this before the text so that any text won't get clipped off CG_DrawZoomMask(); /* if (cg.cameraMode) { return; } */ if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { CG_DrawSpectator(); CG_DrawCrosshair(NULL, 0); CG_DrawCrosshairNames(); } else { // don't draw any status if dead or the scoreboard is being explicitly shown if ( !cg.showScores && cg.snap->ps.stats[STAT_HEALTH] > 0 ) { if ( /*cg_drawStatus.integer*/0 ) { //Reenable if stats are drawn with menu system again Menu_PaintAll(); CG_DrawTimedMenus(); } //CG_DrawTemporaryStats(); CG_DrawAmmoWarning(); CG_DrawCrosshairNames(); if (cg_drawStatus.integer) { CG_DrawIconBackground(); } if (inTime > wpTime) { drawSelect = 1; bestTime = cg.invenSelectTime; } else //only draw the most recent since they're drawn in the same place { drawSelect = 2; bestTime = cg.weaponSelectTime; } if (cg.forceSelectTime > bestTime) { drawSelect = 3; } switch(drawSelect) { case 1: CG_DrawInvenSelect(); break; case 2: CG_DrawWeaponSelect(); break; case 3: CG_DrawForceSelect(); break; default: break; } if (cg_drawStatus.integer) { //Powerups now done with upperright stuff //CG_DrawPowerupIcons(); CG_DrawFlagStatus(); } CG_SaberClashFlare(); if (cg_drawStatus.integer) { CG_DrawStats(); } //Do we want to use this system again at some point? //CG_DrawReward(); } } if (cg.snap->ps.fallingToDeath) { fallTime = (float)(cg.time - cg.snap->ps.fallingToDeath); fallTime /= (FALL_FADE_TIME/2); if (fallTime < 0) { fallTime = 0; } if (fallTime > 1) { fallTime = 1; } hcolor[3] = fallTime; hcolor[0] = 0; hcolor[1] = 0; hcolor[2] = 0; CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor); if (!gCGHasFallVector) { VectorCopy(cg.snap->ps.origin, gCGFallVector); gCGHasFallVector = qtrue; } } else { if (gCGHasFallVector) { gCGHasFallVector = qfalse; VectorClear(gCGFallVector); } } CG_DrawVote(); CG_DrawTeamVote(); CG_DrawLagometer(); if (!cg_paused.integer) { CG_DrawUpperRight(); } if ( !CG_DrawFollow() ) { CG_DrawWarmup(); } // don't draw center string if scoreboard is up cg.scoreBoardShowing = CG_DrawScoreboard(); if ( !cg.scoreBoardShowing) { CG_DrawCenterString(); } } static void CG_DrawTourneyScoreboard() { } /* ===================== CG_DrawActive Perform all drawing needed to completely fill the screen ===================== */ void CG_DrawActive( stereoFrame_t stereoView ) { float separation; vec3_t baseOrg; // optionally draw the info screen instead if ( !cg.snap ) { CG_DrawInformation(); return; } // optionally draw the tournement scoreboard instead if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR && ( cg.snap->ps.pm_flags & PMF_SCOREBOARD ) ) { CG_DrawTourneyScoreboard(); return; } switch ( stereoView ) { case STEREO_CENTER: separation = 0; break; case STEREO_LEFT: separation = -cg_stereoSeparation.value / 2; break; case STEREO_RIGHT: separation = cg_stereoSeparation.value / 2; break; default: separation = 0; CG_Error( "CG_DrawActive: Undefined stereoView" ); } // clear around the rendered view if sized down CG_TileClear(); // offset vieworg appropriately if we're doing stereo separation VectorCopy( cg.refdef.vieworg, baseOrg ); if ( separation != 0 ) { VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg ); } // draw 3D view trap_R_RenderScene( &cg.refdef ); // restore original viewpoint if running stereo if ( separation != 0 ) { VectorCopy( baseOrg, cg.refdef.vieworg ); } // draw status bar and other floating elements CG_Draw2D(); }