/* * 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 "cg_text.h" #include "cg_screenfx.h" #include "../game/q_math.h" /* set in CG_ParseTeamInfo */ int32_t sortedTeamPlayers[TEAM_MAXOVERLAY]; int32_t numSortedTeamPlayers; int32_t drawTeamOverlayModificationCount = -1; /* * TiM: dCross * qboolean CG_WorldCoordToScreenCoord(vec3_t worldCoord, float *x, float *y, qboolean clamp); * end dCross */ /* TiM: Tricorder Parameters */ vec3_t vfwd; vec3_t vright; vec3_t vup; vec3_t vfwd_n; vec3_t vright_n; vec3_t vup_n; int32_t infoStringCount; static qboolean drawCrosshairName=qfalse; extern void InitPostGameMenuStruct(void); static void CG_InterfaceStartup(void); char *ingame_text[IGT_MAX]; /* Holds pointers to ingame text */ int32_t zoomFlashTime=0; interfacegraphics_s interface_graphics[IG_MAX] = { /* type timer x y width height file/text graphic, min max color style ptr */ { SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0 }, /* IG_GROW */ { SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0 }, /* IG_HEALTH_START */ { SG_GRAPHIC, 0.0, 5, 429, 32, 64, "gfx/interface/rpgx_healthbar_leftcorner", 0, 0, 0, CT_DKBROWN1, 0 }, /* IG_HEALTH_BEGINCAP */ { SG_GRAPHIC, 0.0, 64, 429, 6, 25, "gfx/interface/ammobar", 0, 0, 0, CT_DKBROWN1, 0 }, /* IG_HEALTH_BOX1 */ { SG_GRAPHIC, 0.0, 72, 429, 0, 25, "gfx/interface/ammobar", 0, 0, 0, CT_LTBROWN1, 0 }, /* IG_HEALTH_SLIDERFULL */ { SG_GRAPHIC, 0.0, 0, 429, 0, 25, "gfx/interface/ammobar", 0, 0, 0, CT_DKBROWN1, 0 }, /* IG_HEALTH_SLIDEREMPTY */ { SG_GRAPHIC, 0.0, 72, 429, 16, 32, "gfx/interface/rpgx_healthbar_endcap", 0, 0, 147, CT_DKBROWN1, 0 }, /* IG_HEALTH_ENDCAP */ { SG_NUMBER, 0.0, 23, 425, 16, 32, NULL, 0, 0, 0, CT_LTBROWN1, NUM_FONT_BIG }, /* IG_HEALTH_COUNT */ { SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0 }, /* IG_HEALTH_END */ { SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0 }, /* IG_ARMOR_START */ { SG_GRAPHIC, 0.0, 20, 458, 32, 16, "gfx/interface/armorcap1", 0, 0, 0, CT_DKPURPLE1, 0 }, /* IG_ARMOR_BEGINCAP */ { SG_GRAPHIC, 0.0, 64, 458, 6, 12, "gfx/interface/ammobar", 0, 0, 0, CT_DKPURPLE1, 0 }, /* IG_ARMOR_BOX1 */ { SG_GRAPHIC, 0.0, 72, 458, 0, 12, "gfx/interface/ammobar", 0, 0, 0, CT_LTPURPLE1, 0 }, /* IG_ARMOR_SLIDERFULL */ { SG_GRAPHIC, 0.0, 0, 458, 0, 12, "gfx/interface/ammobar", 0, 0, 0, CT_DKPURPLE1, 0 }, /* IG_ARMOR_SLIDEREMPTY */ { SG_GRAPHIC, 0.0, 72, 458, 16, 16, "gfx/interface/armorcap2", 0, 0, 147, CT_DKPURPLE1, 0 }, /* IG_ARMOR_ENDCAP */ { SG_NUMBER, 0.0, 44, 458, 16, 16, NULL, 0, 0, 0, CT_LTPURPLE1, NUM_FONT_SMALL }, /* IG_ARMOR_COUNT */ { SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0 }, /* IG_ARMOR_END */ { SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0 }, /* IG_AMMO_START */ { SG_GRAPHIC, 0.0, 613, 429, 32, 64, "gfx/interface/ammouppercap1", 0, 0, 0, CT_LTPURPLE2, 0 }, /* IG_AMMO_UPPER_BEGINCAP */ { SG_GRAPHIC, 0.0, 607, 429, 16, 32, "gfx/interface/ammouppercap2", 0, 0, 572, CT_LTPURPLE2, 0 }, /* IG_AMMO_UPPER_ENDCAP */ { SG_GRAPHIC, 0.0, 613, 458, 16, 16, "gfx/interface/ammolowercap1", 0, 0, 0, CT_LTPURPLE2, 0 }, /* IG_AMMO_LOWER_BEGINCAP */ { SG_GRAPHIC, 0.0, 578, 458, 0, 12, "gfx/interface/ammobar", 0, 0, 0, CT_LTPURPLE1, 0 }, /* IG_AMMO_SLIDERFULL */ { SG_GRAPHIC, 0.0, 0, 458, 0, 12, "gfx/interface/ammobar", 0, 0, 0, CT_DKPURPLE1, 0 }, /* IG_AMMO_SLIDEREMPTY */ { SG_GRAPHIC, 0.0, 607, 458, 16, 16, "gfx/interface/ammolowercap2", 0, 0, 572, CT_LTPURPLE2, 0 }, /* IG_AMMO_LOWER_ENDCAP */ { SG_NUMBER, 0.0, 573, 425, 16, 32, NULL, 0, 0, 0, CT_LTPURPLE1, NUM_FONT_BIG }, /* IG_AMMO_COUNT */ { SG_VAR, 0.0, 0, 0, 0, 0, NULL, 0, 0, 0, CT_NONE, 0 }, /* IG_AMMO_END */ }; #define LOWEROVERLAY_Y (SCREEN_HEIGHT - ICON_SIZE - 15) /*------------------------------------------------------*/ lensFlare_t lensFlare[MAX_LENS_FLARES]; lensReflec_s lensReflec[10] = { /* width, height, offset, positive, color, shadername, shaders placeholder */ { 23, 23, 0.192, qtrue, { 0.73, 0.50, 0.23 }, "gfx/effects/flares/flare_straight", 0 }, /* Brown1 5.2 */ { 9, 9, 0.37, qtrue, { 0.37, 0.58, 0.55 }, "gfx/effects/flares/flare_straight", 0 }, /* Aqua1 2.7 */ { 14, 14, 0.25, qfalse, { 0.37, 0.79, 0.76 }, "gfx/effects/flares/flare_radial", 0 }, /* Turquoise1 4.0 */ { 86, 86, 0.556, qfalse, { 0.73, 0.50, 0.23 }, "gfx/effects/flares/flare_inverseradial", 0 }, /* BigBrownInverseRad 1.8 */ { 49, 49, 0.476, qfalse, { 0.73, 0.50, 0.23 }, "gfx/effects/flares/flare_straight", 0 }, /* StraightBrown2 2.1 */ { 35, 35, 0.667, qfalse, { 0.34, 0.40, 0.44 }, "gfx/effects/flares/flare_straight", 0 }, /* Grey1 1.5 */ { 32, 32, 0.769, qfalse, { 0.20, 0.38, 0.62 }, "gfx/effects/flares/flare_radial", 0 }, /* BlueRad 1.3 */ { 122, 122, 1.1, qfalse, { 0.31, 0.65, 0.36 }, "gfx/effects/flares/flare_inverseradial", 0 }, /* BigInverseGreen 0.9 */ { 254, 254, 1.429, qfalse, { 1.00, 1.00, 1.00 }, "gfx/effects/flares/flare_chromadisc", 0 }, /* ChromaHoop 0.7 */ { 52, 52, 1.429, qtrue, { 0.40, 0.56, 0.42 }, "gfx/effects/flares/flare_inverseradial", 0 }, /* Green offset 0.7 */ }; #define HALF_SCREEN_WIDTH (SCREEN_WIDTH*0.5) #define HALF_SCREEN_HEIGHT (SCREEN_HEIGHT*0.5) void CG_InitLensFlare( vec3_t worldCoord, int32_t w1, int32_t h1, vec3_t glowColor, float glowOffset, float hazeOffset, int32_t minDist, int32_t maxDist, vec3_t streakColor, int32_t streakDistMin, int32_t streakDistMax, int32_t streakW, int32_t streakH, qboolean whiteStreaks, int32_t reflecDistMin, int32_t reflecDistMax, qboolean reflecAnamorphic, qboolean defReflecs, qboolean clamp, float maxAlpha, int32_t startTime, int32_t upTime, int32_t holdTime, int32_t downTime ) { int32_t i; /* First thing's first.... I understand if you hate flares :'( */ if (!cg_dynamiclensflares.value) return; for (i = 0; i < MAX_LENS_FLARES; i++) { /* find the next free slot */ if ( !lensFlare[i].qfull ) { lensFlare[i].worldCoord[0] = worldCoord[0]; lensFlare[i].worldCoord[1] = worldCoord[1]; lensFlare[i].worldCoord[2] = worldCoord[2]; lensFlare[i].w1 = w1; lensFlare[i].h1 = h1; lensFlare[i].glowColor[0] = glowColor[0]; lensFlare[i].glowColor[1] = glowColor[1]; lensFlare[i].glowColor[2] = glowColor[2]; lensFlare[i].glowOffset = glowOffset; lensFlare[i].hazeOffset = hazeOffset; lensFlare[i].minDist = minDist; lensFlare[i].maxDist = maxDist; lensFlare[i].streakColor[0] = streakColor[0]; lensFlare[i].streakColor[1] = streakColor[1]; lensFlare[i].streakColor[2] = streakColor[2]; lensFlare[i].streakDistMin = streakDistMin; lensFlare[i].streakDistMax = streakDistMax; lensFlare[i].streakW = streakW; lensFlare[i].streakH = streakH; lensFlare[i].whiteStreaks = whiteStreaks; lensFlare[i].reflecDistMin = reflecDistMin; lensFlare[i].reflecDistMax = reflecDistMax; lensFlare[i].reflecAnamorphic = reflecAnamorphic; lensFlare[i].defReflecs = defReflecs; lensFlare[i].clamp = clamp; lensFlare[i].maxAlpha = maxAlpha; lensFlare[i].startTime = startTime; lensFlare[i].upTime = upTime; lensFlare[i].holdTime = holdTime; lensFlare[i].downTime = downTime; lensFlare[i].qfull = qtrue; break; } } } /* ================= CG_WorldCoordToScreenCoord **Blatently plagiarised from EF SP** OMFG this is some damn whacky maths! It basically takes a vector variable and somehow correlates that to an XY value on your screen!! O_o ================= */ static qboolean CG_WorldCoordToScreenCoord(vec3_t worldCoord, float *x, float *y, qboolean clamp) { int32_t xcenter, ycenter; vec3_t local, transformed; vec3_t fwd; vec3_t right; vec3_t up; float xzi; float yzi; /* * NOTE: did it this way because most draw functions expect virtual 640x480 coords * and adjust them for current resolution */ xcenter = 640 >> 1; ycenter = 480 >> 1; AngleVectors (cg.refdefViewAngles, fwd, right, up); VectorSubtract (worldCoord, cg.refdef.vieworg, local); transformed[0] = DotProduct(local,right); transformed[1] = DotProduct(local,up); transformed[2] = DotProduct(local,fwd); /* Make sure Z is not negative. */ if(transformed[2] < 0.01) { if ( clamp ) { transformed[2] = 0.01f; } else { return qfalse; } } /* Simple convert to screen coords. */ xzi = xcenter / transformed[2] * (96.0/cg.refdef.fov_x);/*90*/ /*95*/ yzi = ycenter / transformed[2] * (102.0/cg.refdef.fov_y);/*90*/ /*105*/ *x = (float)(xcenter + xzi * transformed[0]); *y = (float)(ycenter - yzi * transformed[1]); return qtrue; } /************************************* CG_FlareScreenTrans - TiM Used to return an alpha value based on how far the xy value is from two boundaries (Used mainly for when the flare exits the screen and fades out) The function works by drawing an imaginary line from the minimum point to the maximum point. If a point is above that line, the Y value is used to calculate the alpha, else, the X value does. There is a slight bit of jerkiness if the point crosses this line, but much less worse than what was before. :) *************************************/ static float CG_FlareScreenTrans(int32_t x, int32_t y, int32_t xmin, int32_t ymin, int32_t xmax, int32_t ymax ) { /* * Think about it, when the XY points are in separate quadrants of the screen, * they're all the same values anyway, but just either negative or positive. * Making them all positive, and working on just that set kills about 8 birds with a fricken' huge stone. >:) */ int32_t lx = abs(x); int32_t ly = abs(y); int32_t lxmin = abs(xmin); int32_t lymin = abs(ymin); int32_t lxmax = abs(xmax); int32_t lymax = abs(ymax); int32_t xDif = lxmax - lxmin; int32_t yDif = lymax - lymin; float grad = ( (float)lymax/(float)lxmax ); /* calc the grad as if (xmin, ymin) were the origin */ float alpha = 1.0; /* if xy is under minimums, just make it 1 :P */ if (lx < lxmin && ly < lymin ) { return alpha; } if ( ly < (lx * grad) ) {/* point is running along the side bar */ alpha = (float)( 1.0 - ( (float)lx - (float)lxmin ) / (float)xDif ); } if ( ly > ( lx * grad) ) {/* point is running along the top bar */ alpha = (float)( 1.0 - ( (float)ly - (float)lymin ) / (float)yDif ); } /* if xy has exceeded maxes, just make it 0 :P */ if ( lx >= lxmax || ly >= lymax ) alpha = 0.0; /* Lock it just in case something weird happened. :S */ if ( alpha > 1.0 ) alpha = 1.0; if ( alpha < 0.0 ) alpha = 0.0; return alpha; } /* ================ CG_CorrelateMaxMinDist Calcuates an alpha value between a min and a max point so elements can fade in or out depending on relative distance :) ================ */ static float CG_CorrelateMaxMinDist( float len, int32_t min, int32_t max ) { float alpha = 1.0; if ( min == max && max == 0 ) /* This means it will always be off */ return 0.0; if ( min <= 0 ) /* this means that the parameter wants it to always be on */ return alpha; alpha = ( len - (float)min ) / ((float)max - (float)min); /* calculate the alpha */ if (alpha > 1.0 ) /* Clamp it.... again */ alpha = 1.0; if (alpha < 0.0 ) alpha = 0.0; return alpha; } /* ================ CG_FadeAlpha Modified version of CG_FadeColor. Only covers alpha values now, and also has an option to fade in as well as out ================ */ float CG_FadeAlpha( int32_t startMsec, int32_t totalMsec, qboolean fade_in ) { static float alpha; int32_t t; if ( startMsec == 0 ) { return (fade_in ? 0.0 : 1.0); } t = cg.time - startMsec; if ( t >= totalMsec ) { return (fade_in ? 1.0 : 0.0); } // fade out if ( totalMsec - t < FADE_TIME ) { if (!fade_in) alpha = ( totalMsec - t ) * 1.0/FADE_TIME; else alpha = 1.0 - (( totalMsec - t ) * 1.0/FADE_TIME); } else { alpha = fade_in ? 0.0 : 1.0; } return alpha; } /* ================ CG_FlareTraceTrans Performs a trace between player and origin, and if anything gets in the way, an alpha value is generated to make the flare fade out ================ */ static float prevFrac = 0.0; static int32_t fadeTime, fadeInTime; static qboolean CG_FlareTraceTrans ( vec3_t origin, float* alpha ) { trace_t trace; CG_Trace( &trace, origin, NULL, NULL, cg.refdef.vieworg, -1, CONTENTS_SOLID|CONTENTS_BODY ); //Do a trace // switched start and end if ( fadeTime > 0 && fadeInTime == 0 ) { *alpha = CG_FadeAlpha( fadeTime, 199, qfalse ); if (*alpha == 0.0) fadeTime = 0.0f; } if ( fadeInTime > 0 && fadeTime == 0 ) { *alpha = CG_FadeAlpha( fadeInTime, 199, qtrue ); if (*alpha == 1.0) fadeInTime = 0.0f; } //fade out the flare if (trace.fraction < 1.0 && prevFrac == 1.0 ) { fadeTime = cg.time; prevFrac = trace.fraction; } //fade in the flare if (trace.fraction == 1.0 && prevFrac < 1.0 ) { fadeInTime = cg.time; prevFrac = trace.fraction; } if (fadeTime > 0 && fadeInTime > 0) { //Whoa, how did this happen??? fadeTime = 0; //reset them both and all is good :) fadeInTime = 0; } if ( (fadeTime == 0.0 && fadeInTime == 0.0 ) && ( *alpha > 0.0 && *alpha < 1.0 ) ) //Now THIS effect was weird O_o *alpha = 1.0; if (trace.fraction < 1.0 && prevFrac < 1.0 && fadeTime == 0 && fadeInTime == 0) { prevFrac = trace.fraction; return qfalse; } return qtrue; } /************************************************************* CG_DrawLensFlare - RPG-X : TiM OMFG LENSFLARES R COOL!!!!! ^_^!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Yes, I know I'm over-doing it now, coding this uber-huge processor-intensive, totally un-necessary lensflare engine ;P Parameters Key: vec3_t worldCoord : Position in world to draw the flare int32_t w1, h1 : Initial (Maximum) w + h of the flare core vec3_t glowColor : Color of the flare's glow float glowOffset : Multiplier how much bigger the glow is than the core float hazeOffset : Multiplier how much bigger the surrounding haze is to the core int32_t minDist : Minimum distance before the flare loses all brightness (Set to 0 if always normal size) int32_t maxDist : Maximum distance for flare's brightness vec3_t streakColor : Color of the flare's lens reflections (if 0,0,0, then a default blue is used) int32_t streakDistMin : Distance at where the flare is totally transparent (Set to 0 if always on) int32_t streakDistMax : Distance at where the flare is totally opaque (Set to same as above to turn it always off) int32_t streakW : Length of the anamorphic lens streak int32_t streakH : Height of the anamorphic lens streak qboolean whiteStreaks : Adds white streaks to the center of normal streaks ;P int32_t reflecDistMin : Distance at where the reflections are totally transparent (Set to NULL if always on) int32_t reflecDistMax : Distance at where the reflections are totally opaque (Set to same value as above if wanted off) qboolean reflecAnamorphic : Enables anamorphic lens reflections qboolean defReflecs : Makes the Lens Reflections default colors qboolean clamp : If qtrue, the lensflare will not resize as the distance changes float maxAlpha : All alpha values of the elements in the flare will not exceed this number int32_t upTime : How long it takes for the flare to go from 0 intense to maximum intense int32_t holdTime : How long the flare stays at max intensity for int32_t downTime : How long it takes for the flare to go from max intensity to 0. **************************************************************/ void CG_DrawLensFlare( lensFlare_t *flare ) { int32_t w = flare->w1; int32_t h = flare->h1; float x, y, streakX, streakY; int32_t xCart, yCart; vec4_t color, reflecColor, strkColor; int32_t xMax, yMax; vec3_t distDif, black = {0.0, 0.0, 0.0}; int32_t maxTime = flare->upTime + flare->holdTime + flare->downTime; int32_t tMaxTime = maxTime + flare->startTime; int32_t tUpTime = flare->upTime + flare->startTime; int32_t tHoldTime = flare->upTime + flare->holdTime + flare->startTime; int32_t tDownTime = flare->upTime + flare->holdTime + flare->downTime + flare->startTime; float length; float reflecAlpha = 1.0; //alpha channel of reflections float streakAlpha = 1.0; //alpha channel of streaks float boundAlpha = 1.0; //alpha if flare leaves screen float commonAlpha = 1.0; //alpha variables common too all elements float hazeAlpha = 1.0; static float fadeAlpha; //This can't have a default value otherwise it screws up the flare fade transition static float timeAlpha; //Alpha/w/h over the specified time //First thing's first.... I understand if you hate flares :'( if (!cg_dynamiclensflares.value) return; //if we can't get an XY value, screw it :P if ( !CG_WorldCoordToScreenCoord( flare->worldCoord, &x, &y, qfalse) ) return; //if we can't actually see the flare in line of sight, screw it again. :P if( !CG_FlareTraceTrans( flare->worldCoord, &fadeAlpha) ) return; if (maxTime > 0 && cg.time <= tMaxTime) { if ( cg.time <= tUpTime ) timeAlpha = (float)(cg.time - flare->startTime) * (float)(1.0/(float)flare->upTime); if (cg.time <= tHoldTime && cg.time > tUpTime ) timeAlpha = 1.0; if (cg.time <= tDownTime && cg.time > tHoldTime ) timeAlpha = 1.0 - ( (float)(cg.time - flare->startTime) * (float)(1.0/(float)flare->downTime) ); } if (maxTime == 0 ) timeAlpha = 1.0; w = w * timeAlpha; h = h * timeAlpha; //calc the distance between the player and the flare VectorSubtract( flare->worldCoord, cg.refdef.vieworg, distDif ); length = VectorNormalize( distDif ); //if the clamp boolean is false, resize the flare over player distance from it if ( !flare->clamp ) { w = w * CG_CorrelateMaxMinDist(length, flare->minDist, flare->maxDist ); //Change size/height in relation to distance h = h * CG_CorrelateMaxMinDist(length, flare->minDist, flare->maxDist ); } xCart = (int32_t)(x - HALF_SCREEN_WIDTH ); //Re-orient the EF drawing engine so co-ord (0,0) is in the middle of the screen) yCart = (int32_t)(y - HALF_SCREEN_HEIGHT ); streakX = (xCart - (flare->streakW*0.5)) + HALF_SCREEN_WIDTH; //Calculate X value of lens streak based on flare position streakY = (yCart - (flare->streakH*0.5)) + HALF_SCREEN_HEIGHT; //Calculate Y value of lens streak based on flare position xMax = (w*0.5) + HALF_SCREEN_WIDTH; //define the point the flare should fully fade out yMax = (h*0.5) + HALF_SCREEN_HEIGHT; if ( boundAlpha > 0.0 ) { //Calculate the reflections' opacity in contrast to the edge of the screen boundAlpha = CG_FlareScreenTrans( xCart, yCart, HALF_SCREEN_WIDTH, HALF_SCREEN_HEIGHT, xMax, yMax); } //set up all of the elements with their various alphas :P commonAlpha = commonAlpha * fadeAlpha * boundAlpha * flare->maxAlpha; if (commonAlpha * timeAlpha < 0.01 ) //no point in drawing if it's really really faint return; reflecAlpha = reflecAlpha * commonAlpha * timeAlpha * CG_CorrelateMaxMinDist(length, flare->reflecDistMin, flare->reflecDistMax ); streakAlpha = streakAlpha * commonAlpha * timeAlpha * CG_CorrelateMaxMinDist(length, flare->streakDistMin, flare->streakDistMax ); hazeAlpha = hazeAlpha * commonAlpha; //Copy in the color the user wants, but we need control of the alpha. VectorCopy( flare->glowColor, color ); color[3] = hazeAlpha; if ( VectorCompare( flare->streakColor, black) ) { //If they specified no streakcolor, use this awesome default blue one :) strkColor[0] = 0.31; strkColor[1] = 0.45; strkColor[2] = 1.0; strkColor[3] = streakAlpha; } else { //else, use the color they wanted VectorCopy( flare->streakColor, strkColor ); strkColor[3] = streakAlpha; } //Lens Reflections - those cool circly bits that go in the opposite direction of the flare if ( reflecAlpha != 0.0 ) {//Sheez, only do this if we really WANT it O_o int32_t i; for( i = 0; i < 10; i++ ) { //if they wanted the cool photoshoppy style reflections if ( flare->defReflecs ) { VectorCopy( lensReflec[i].color, reflecColor ); reflecColor[3] = reflecAlpha; } else { //otherwise, just use the color they picked VectorCopy( color, reflecColor ); reflecColor[3] = reflecAlpha; } trap_R_SetColor( reflecColor ); CG_DrawPic( ( ( ( lensReflec[i].positive ? xCart : -xCart ) * lensReflec[i].offset ) + HALF_SCREEN_WIDTH ) - ( flare->reflecAnamorphic ? lensReflec[i].width : lensReflec[i].width*0.5 ), //X ( ( ( lensReflec[i].positive ? yCart : -yCart ) * lensReflec[i].offset ) + HALF_SCREEN_HEIGHT ) - ( lensReflec[i].height*0.5 ), //Y flare->reflecAnamorphic ? lensReflec[i].width * 2 : lensReflec[i].width, //W lensReflec[i].height, //H lensReflec[i].graphic //pic ); } } //Colored Middle + Streaks trap_R_SetColor( color ); if ( color[3] > 0.0 ) { x = ( xCart - ( (w*flare->hazeOffset) *0.5) + HALF_SCREEN_WIDTH ); y = ( yCart - ( (h*flare->hazeOffset) *0.5) + HALF_SCREEN_HEIGHT ); CG_DrawPic( x, y, w*flare->hazeOffset, h*flare->hazeOffset, cgs.media.flareHaze ); //Surrounding ambient haze } trap_R_SetColor( strkColor ); if ( strkColor[3] > 0.0f ) CG_DrawPic( streakX , streakY , flare->streakW, flare->streakH, cgs.media.flareStreak ); //Colored portion of the anamorphic streaks trap_R_SetColor( color ); if ( color[3] > 0.0f ) { x = ( xCart - ( (w*flare->glowOffset) *0.5) + HALF_SCREEN_WIDTH ); y = ( yCart - ( (h*flare->glowOffset) *0.5) + HALF_SCREEN_HEIGHT ); CG_DrawPic( x, y, w*flare->glowOffset, h*flare->glowOffset, cgs.media.flareCore ); //Main colored glow bit of the main flare } if ( flare->whiteStreaks ) { //if player wanted white streaks in their streaks strkColor[0] = strkColor[1] = strkColor[2] = 1.0; trap_R_SetColor( strkColor ); //White if ( strkColor[3] > 0.0 ) CG_DrawPic( streakX + (flare->streakW*0.2), streakY + (flare->streakH*0.2), flare->streakW*0.6, flare->streakH*0.6, cgs.media.flareStreak ); //White Core of streak is ALWAYS 20% smaller. } color[0] = color[1] = color [2] = 1.0f; color[3] = hazeAlpha; trap_R_SetColor( color ); if ( color[3] > 0.0 ) { x = ( xCart - (w *0.5) + HALF_SCREEN_WIDTH ); y = ( yCart - (h *0.5) + HALF_SCREEN_HEIGHT ); CG_DrawPic( x, y, w, h, cgs.media.flareCore ); //Draw teh main fl4r3 :) } } /* ================ CG_Draw3DModel ================ */ static void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, qhandle_t shader, vec3_t origin, vec3_t angles ) { refdef_t refdef; refEntity_t ent; if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) { return; } CG_AdjustFrom640( &x, &y, &w, &h ); 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.customShader = shader; 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, int32_t clientNum, vec3_t headAngles ) { clipHandle_t cm; clientInfo_t *ci; playerState_t *ps; float value; vec3_t origin; vec3_t mins, maxs; ci = &cgs.clientinfo[ clientNum ]; ps = &cg.snap->ps; value = ps->stats[STAT_HEALTH]; if ( cg_draw3dIcons.integer && (ci->headOffset[0] != 404) ) { cm = ci->headModel; if ( !cm ) { return; } // offset the origin y and z to center the head 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 head nearly fills the box // assume heads are taller than wide float len = 0.7 * ( maxs[2] - mins[2] ); origin[0] = len / 0.268; // len / tan( fov/2 ) // allow per-model tweaking VectorAdd( origin, ci->headOffset, origin ); CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, 0, origin, headAngles ); if ((value < 82.000000) && (value >= 65.000000)){ CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, 0, origin, headAngles ); }else if(value >= 49.000000){ CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, 0, origin, headAngles ); }else if(value >= 32.000000){ CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, 0, origin, headAngles ); }else if(value >= 2.000000){ CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, 0, origin, headAngles ); }else if(value <= 1.000000){ CG_Draw3DModel( x, y, w, h, ci->headModel, ci->headSkin, 0, origin, headAngles ); } } else if ( cg_drawIcons.integer ) { CG_DrawPic( x, y, w, h, ci->modelIcon ); } } /* ================ CG_DrawFlagModel Used for both the status bar and the scoreboard ================ */ void CG_DrawFlagModel( float x, float y, float w, float h, int32_t team ) { qhandle_t cm; vec3_t origin, angles; vec3_t mins, maxs; if ( 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 float len = 0.5 * ( maxs[2] - mins[2] ); origin[0] = len / 0.268; // len / tan( fov/2 ) angles[YAW] = 60 * sin( cg.time / 2000.0 );; CG_Draw3DModel( x, y, w, h, team == TEAM_RED ? cgs.media.redFlagModel : cgs.media.blueFlagModel, 0, team == TEAM_RED ? cgs.media.redFlagShader[3] : cgs.media.blueFlagShader[3], origin, angles ); } } /* ================ CG_DrawStatusBarHead RPG-X | Phenix | 09/06/2005 I dont know who commented this out but it's going back in ;) ================ */ static int32_t CG_DrawStatusBarHead( float x ) { vec3_t angles; float size; float frac; VectorClear( angles ); if ( cg.damageTime && cg.time - cg.damageTime < DAMAGE_TIME ) { frac = (float)(cg.time - cg.damageTime ) / DAMAGE_TIME; size = ICON_SIZE * 1.25 * ( 1.5 - frac * 0.5 ); float stretch = size - ICON_SIZE * 1.25; // kick in the direction of damage x -= stretch * 0.5 + cg.damageX * stretch * 0.5; cg.headStartYaw = 180 + cg.damageX * 45; cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); cg.headEndPitch = 5 * cos( crandom()*M_PI ); cg.headStartTime = cg.time; cg.headEndTime = cg.time + 100 + random() * 2000; } else { if ( cg.time >= cg.headEndTime ) { // select a new head angle cg.headStartYaw = cg.headEndYaw; cg.headStartPitch = cg.headEndPitch; cg.headStartTime = cg.headEndTime; cg.headEndTime = cg.time + 100 + random() * 2000; cg.headEndYaw = 180 + 20 * cos( crandom()*M_PI ); cg.headEndPitch = 5 * cos( crandom()*M_PI ); } size = ICON_SIZE * 1.25; } size = size * 3; // if the server was frozen for a while we may have a bad head start time if ( cg.headStartTime > cg.time ) { cg.headStartTime = cg.time; } frac = ( cg.time - cg.headStartTime ) / (float)( cg.headEndTime - cg.headStartTime ); frac = frac * frac * ( 3 - 2 * frac ); angles[YAW] = cg.headStartYaw + ( cg.headEndYaw - cg.headStartYaw ) * frac; angles[PITCH] = cg.headStartPitch + ( cg.headEndPitch - cg.headStartPitch ) * frac; CG_DrawHead( x, 480 - (size + BIGCHAR_HEIGHT + 5), size, size, cg.snap->ps.clientNum, angles ); return size; } /* ================ CG_DrawTeamBackground ================ */ void CG_DrawTeamBackground( int32_t x, int32_t y, int32_t w, int32_t h, float alpha, int32_t team, qboolean scoreboard ) { vec4_t hcolor; hcolor[3] = alpha; if ( team == TEAM_RED ) { hcolor[0] = 1; hcolor[1] = 0; hcolor[2] = 0; } else if ( team == TEAM_BLUE ) { hcolor[0] = 0; hcolor[1] = 0; hcolor[2] = 1; } else { return; // no team } trap_R_SetColor( hcolor ); CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar ); trap_R_SetColor( NULL ); } /* ================ CG_DrawAmmo ================ */ static void CG_DrawAmmo(centity_t *cent) { // unused function ... lol might be removed return; } static int32_t CG_DrawHealth(centity_t *cent) { float value; playerState_t *ps; char *health_str = NULL; int32_t health_barwidth; vec_t *health_txtcolor = NULL; int32_t health_txteffect = 0; ps = &cg.snap->ps; value = ps->stats[STAT_HEALTH]; //RPG-X: RedTechie - The GROSS math part icky icky! if(value >= 82.000000){ health_str = ingame_text[IGT_SB_HEALTHSTATUS1]; //RPG-X and SFEF health_txtcolor = colorTable[CT_DKPURPLE2]; health_txteffect = UI_BIGFONT; }else if(value >= 65.000000){ health_str = ingame_text[IGT_SB_HEALTHSTATUS2]; //Scott Carter, after being locked in a room with rpg-x team for 20 minuts.. health_txtcolor = colorTable[CT_DKPURPLE2]; health_txteffect = UI_BIGFONT; }else if(value >= 49.000000){ health_str = ingame_text[IGT_SB_HEALTHSTATUS3]; //Results after 10 minutes health_txtcolor = colorTable[CT_LTBLUE2]; health_txteffect = UI_BIGFONT; }else if(value >= 32.000000){ health_str = ingame_text[IGT_SB_HEALTHSTATUS4]; //Results after 15 minutes health_txtcolor = colorTable[CT_LTBLUE2]; health_txteffect = UI_BIGFONT; }else if(value >= 2.000000){ health_str = ingame_text[IGT_SB_HEALTHSTATUS5]; //Results after 20 minutes health_txtcolor = colorTable[CT_VDKBLUE2]; health_txteffect = UI_BIGFONT; }else if(value <= 1.000000){ health_str = ingame_text[IGT_SB_HEALTHSTATUS6]; //Final result - post your comments here coders ;) - //What do you mean final result - this is like after 30 seconds in a room with the rpg-x team :P (Phenix) health_txtcolor = colorTable[CT_RED]; //More like 10 -TiM health_txteffect = UI_BIGFONT; } //Get a accurate width health_barwidth = UI_ProportionalStringWidth(health_str,UI_BIGFONT); //Doom Style Health! if (doomHead.integer == 1) { health_barwidth = CG_DrawStatusBarHead( 2 ); health_barwidth = ((health_barwidth / 2) + 2) - (UI_ProportionalStringWidth(health_str,UI_BIGFONT) / 2); UI_DrawProportionalString(health_barwidth, 460 - BIGCHAR_HEIGHT, health_str, health_txteffect, health_txtcolor); return health_barwidth; } else { int32_t x = 3; int32_t y = 435; //Draw the text UI_DrawProportionalString(x + 46, y + 11, health_str, health_txteffect, health_txtcolor); //RPG-X: - RedTechie The Graphics :) trap_R_SetColor( colorTable[CT_DKBLUE1] ); CG_DrawPic( x, y, 85, 22, cgs.media.healthbigcurve ); //RPG-X: Big Curve //x,y,w,h=32 CG_DrawPic( x + 49 + health_barwidth, y, 8, 7, cgs.media.healthendcap ); //RPG-X: Top End Cap - 133 CG_DrawPic( x + 49 + health_barwidth, y + 37, 8, 7, cgs.media.healthendcap ); //RPG-X: Bottum End Cap - 133 //CG_DrawPic( x + 49 + health_barwidth, y + 49, 16, 16, cgs.media.healthendcap ); CG_FillRect( x, y + 15, 40, 20, colorTable[CT_DKBLUE1]); //Extra bit to fill in the gap under the curve graphic CG_FillRect( x, y + 37, 40, 7, colorTable[CT_DKGOLD1]); //RPG-X: Middle bar //15 CG_FillRect( x + 42, y + 37, 5+health_barwidth, 7, colorTable[CT_DKBLUE1]); //RPG-X: Bottum Horizontal bar - CG_FillRect( 45, 469, 86+health_barwidth, 15, colorTable[CT_DKBLUE1]); CG_FillRect( x + 47, y, health_barwidth, 7, colorTable[CT_DKBLUE1]); //RPG-X: Top Horizontal bar - CG_FillRect( 61, 420, 70+health_barwidth, 15, colorTable[CT_DKBLUE1]); //RPG-X: RedTechie - Some eye candy text UI_DrawProportionalString( x +40-3, y + 23, ingame_text[IGT_SB_HEALTHBARLCARS], UI_TINYFONT|UI_RIGHT, colorTable[CT_BLACK]); return health_barwidth + 82; } } static float CG_DrawShipHealth( void ) { CG_FillRect( 3, 400, 102, 31, colorTable[CT_BLACK]); //shield CG_FillRect( 4, 401, 100, 14, colorTable[CT_VDKBLUE1]); if(cg.shieldState == 1){ CG_FillRect( 4, 401, floor(cg.relativeHullStrength), 14, colorTable[CT_DKBLUE1]); UI_DrawProportionalString(5, 403, va("Shields: %i Pct.", cg.relativeHullStrength), UI_TINYFONT, colorTable[CT_MDGREY]); } else if(cg.shieldState == 0){ UI_DrawProportionalString(5, 403, "Shields: standby", UI_TINYFONT, colorTable[CT_MDGREY]); } else if(cg.shieldState == -2){ UI_DrawProportionalString(5, 403, "Shields: offline", UI_TINYFONT, colorTable[CT_MDGREY]); } else if(cg.shieldState == -1){ UI_DrawProportionalString(5, 403, "Shields: inoperable", UI_TINYFONT, colorTable[CT_MDGREY]); } //hull CG_FillRect( 4, 416, 100, 14, colorTable[CT_VDKRED1]); CG_FillRect( 4, 416, floor(cg.relativeHullStrength), 14, colorTable[CT_DKRED1]); UI_DrawProportionalString(5, 418, va("Structural: %i Pct.", cg.relativeHullStrength), UI_TINYFONT, colorTable[CT_MDGREY]); return 0; } /* ================ CG_DrawStatusBar ================ */ static void CG_DrawStatusBar( void ) { centity_t *cent; vec3_t angles; int32_t y=0; vec4_t whiteA; vec3_t tmpVec, eAngle, forward, dAngle; int32_t healthBarWidth; whiteA[0] = whiteA[1] = whiteA[2] = 1.0f; whiteA[3] = 0.3f; cent = &cg_entities[cg.snap->ps.clientNum]; if ( cg_drawStatus.integer == 0 ) { return; } // draw the team background CG_DrawTeamBackground( 0, 420, 640, 60, 0.33, cg.snap->ps.persistant[PERS_TEAM], qfalse ); VectorClear( angles ); // draw any 3D icons first, so the changes back to 2D are minimized y = (SCREEN_HEIGHT - (4*ICON_SIZE) - 20); // Do start if (!cg.interfaceStartupDone) { CG_InterfaceStartup(); } // // ammo // if ( cent->currentState.weapon ) { CG_DrawAmmo(cent); } // // health // //RPG-X | Phenix | 09/06/2005 // Added return of the width for the cloak etc messages healthBarWidth = CG_DrawHealth(cent); //shiphealth if(cg.relativeHullStrength > 0) CG_DrawShipHealth(); // RPG-X // Print RPG Flags //By: RedTechie & Phenix // if(cg.predictedPlayerState.powerups[PW_EVOSUIT] || cg.predictedPlayerState.powerups[PW_FLIGHT] || cg.predictedPlayerState.powerups[PW_INVIS]){ //RPG-X | Phenix | 08/06/2005 int32_t yZ = 478 - SMALLCHAR_HEIGHT; // UI_BIGFONT //DEBUG if(cg.predictedPlayerState.powerups[PW_EVOSUIT]) { UI_DrawProportionalString(healthBarWidth, yZ, ingame_text[IGT_SB_EVOSUITSTATUS], UI_SMALLFONT, colorTable[CT_CYAN]); yZ -= SMALLCHAR_HEIGHT + 2; } if(cg.predictedPlayerState.powerups[PW_INVIS]){ UI_DrawProportionalString(healthBarWidth, yZ, ingame_text[IGT_SB_CLOAKSTATUS], UI_SMALLFONT, colorTable[CT_RED]); yZ -= SMALLCHAR_HEIGHT + 2; } if(cg.predictedPlayerState.powerups[PW_FLIGHT]){ UI_DrawProportionalString(healthBarWidth, yZ, ingame_text[IGT_SB_FLIGHTSTATUS], UI_SMALLFONT, colorTable[CT_RED]); yZ -= SMALLCHAR_HEIGHT + 2; } } // // armor // //RPG-X: - Redtechie IT A FRICKEN RP NOOOO ARMOR! OMG! //CG_DrawArmor(cent); // Radar // By Sam "-=Jazz=-"Dickinson // http://www.telefragged.com/jazz if ( ( cg.snap->ps.weapon == WP_2 || cg.snap->ps.weapon == WP_6 ) && cg_drawradar.integer != 0 && !cg.zoomed ) { vec4_t radColor; CG_DrawPic(40, 100, 100, 100, cgs.media.radarShader); int32_t i; for (i = 0; i < cg.snap->numEntities; i++) // Go through all entities in VIS range { if ( cg.snap->entities[i].eType == ET_PLAYER ) // If the Entity is a Player { /*if(cg.snap->entities[i].time == -1){ CG_Printf("Radar: decoy found, going to next ent\n"); continue; }*/ CG_Printf("Radar: Player found on pos %d with enum %d -> do vector math\n", i, cg.snap->entities[i].number); // Calculate How Far Away They Are int32_t x = (cg.snap->entities[i].pos.trBase[0] - cg.predictedPlayerState.origin[0]); y = (cg.snap->entities[i].pos.trBase[1] - cg.predictedPlayerState.origin[1]); int32_t z = (cg.snap->entities[i].pos.trBase[2] - cg.predictedPlayerState.origin[2]); tmpVec[0] = x; tmpVec[1] = y; tmpVec[2] = 0.0; // Convert Vector to Angle vectoangles(tmpVec, eAngle); int32_t h = sqrt((x*x) + (y*y)); // Get Range // We only Want "YAW" value dAngle[0] = 0.0; dAngle[1] = AngleSubtract(eAngle[1] - 180, cg.predictedPlayerState.viewangles[1]) + 180; dAngle[0] = 0.0; // Convert Angle back to Vector AngleVectors(dAngle, forward, NULL, NULL); VectorScale(forward, h/32, forward); CG_Printf("Radar: are you dead?\n"); //RPG-X: RedTechie - If Dead show them as a medical symbol //.number if (h/32 < 100 && h/32 > 0) { // Limit Radar Range if ( cg_entities[cg.snap->entities[i].number].currentState.eFlags & EF_DEAD ) { CG_Printf("Radar: He's dead, Jim!, draw dot\n"); if (z > 64) { CG_DrawStretchPic( 86 - forward[1], 146 - forward[0], 16, 8, 0, 0, 1, 0.5, cgs.media.rd_injured_level ); } else if (z < -64) { CG_DrawStretchPic( 86 - forward[1], 146 - forward[0], 16, 8, 0, 0.5, 1, 1, cgs.media.rd_injured_level ); } else { CG_DrawPic(86 - forward[1], 146 - forward[0], 16, 16, cgs.media.rd_injured_level); } } else { CG_Printf("Radar: He's not dead, Jim!\n"); if(cg.snap->entities[i].time != -1) if ( cgs.clientinfo[cg.snap->entities[i].number].pClass >= 0 && cg.snap->entities[i].time != -1 ) { CG_Printf("Radar: color picked from class\n"); radColor[0] = (float)cgs.classData[cgs.clientinfo[cg.snap->entities[i].number].pClass].radarColor[0] / 255.0f; radColor[1] = (float)cgs.classData[cgs.clientinfo[cg.snap->entities[i].number].pClass].radarColor[1] / 255.0f; radColor[2] = (float)cgs.classData[cgs.clientinfo[cg.snap->entities[i].number].pClass].radarColor[2] / 255.0f; radColor[3] = 1.0f; CG_Printf("Radar: coloring complete: 0 = %.3f -- 1 = %.3f -- 2 = %.3f\n", radColor[0], radColor[1], radColor[2]); } else { CG_Printf("Radar: color picked by default as black\n"); VectorCopy( colorTable[CT_BLACK], radColor ); radColor[3] = colorTable[CT_BLACK][3]; CG_Printf("Radar: coloring complete as black\n"); } else { CG_Printf("Radar: Decoy found, color picked as black\n"); VectorCopy( colorTable[CT_BLACK], radColor ); radColor[3] = colorTable[CT_BLACK][3]; CG_Printf("Radar: coloring complete as black\n"); } if ( cgs.clientinfo[cg.snap->entities[i].number].isAdmin && !cgs.clientinfo[cg.snap->ps.clientNum].isAdmin ) { CG_Printf("Radar: hide admins from non admins\n"); continue; } CG_Printf("Radar: Draw Dot\n"); if ( z > 64 ) { trap_R_SetColor( radColor ); CG_DrawStretchPic( 86 - forward[1], 146 - forward[0], 8, 4, 0, 0, 1, 0.5, cgs.media.radarMain ); } else if ( z < -64 ) { trap_R_SetColor( radColor ); CG_DrawStretchPic( 86 - forward[1], 146 - forward[0], 8, 4, 0, 0.5, 1, 1, cgs.media.radarMain ); } else { trap_R_SetColor( radColor ); CG_DrawPic( 86 - forward[1], 146 - forward[0], 8, 8, cgs.media.radarMain ); } trap_R_SetColor( NULL ); CG_Printf("Radar: Dot Placed\n"); } } } } } // End Radar } /* ================ CG_InterfaceStartup ================ */ static void CG_InterfaceStartup() { // Turn on Health Graphics if ((interface_graphics[IG_HEALTH_START].timer < cg.time) && (interface_graphics[IG_HEALTH_BEGINCAP].type == SG_OFF)) { trap_S_StartLocalSound( cgs.media.interfaceSnd1, CHAN_LOCAL_SOUND ); interface_graphics[IG_HEALTH_BEGINCAP].type = SG_GRAPHIC; interface_graphics[IG_HEALTH_BOX1].type = SG_GRAPHIC; interface_graphics[IG_HEALTH_ENDCAP].type = SG_GRAPHIC; } // Turn on Ammo Graphics if (interface_graphics[IG_AMMO_START].timer < cg.time) { if (interface_graphics[IG_AMMO_UPPER_BEGINCAP].type == SG_OFF) { trap_S_StartLocalSound( cgs.media.interfaceSnd1, CHAN_LOCAL_SOUND ); interface_graphics[IG_GROW].type = SG_VAR; interface_graphics[IG_GROW].timer = cg.time; } interface_graphics[IG_AMMO_UPPER_BEGINCAP].type = SG_GRAPHIC; interface_graphics[IG_AMMO_UPPER_ENDCAP].type = SG_GRAPHIC; interface_graphics[IG_AMMO_LOWER_BEGINCAP].type = SG_GRAPHIC; interface_graphics[IG_AMMO_LOWER_ENDCAP].type = SG_GRAPHIC; } if (interface_graphics[IG_GROW].type == SG_VAR) { interface_graphics[IG_HEALTH_ENDCAP].x += 2; interface_graphics[IG_ARMOR_ENDCAP].x += 2; interface_graphics[IG_AMMO_UPPER_ENDCAP].x -= 1; interface_graphics[IG_AMMO_LOWER_ENDCAP].x -= 1; if (interface_graphics[IG_HEALTH_ENDCAP].x >= interface_graphics[IG_HEALTH_ENDCAP].max) { interface_graphics[IG_HEALTH_ENDCAP].x = interface_graphics[IG_HEALTH_ENDCAP].max; interface_graphics[IG_ARMOR_ENDCAP].x = interface_graphics[IG_ARMOR_ENDCAP].max; interface_graphics[IG_AMMO_UPPER_ENDCAP].x = interface_graphics[IG_AMMO_UPPER_ENDCAP].max; interface_graphics[IG_AMMO_LOWER_ENDCAP].x = interface_graphics[IG_AMMO_LOWER_ENDCAP].max; interface_graphics[IG_GROW].type = SG_OFF; interface_graphics[IG_HEALTH_SLIDERFULL].type = SG_GRAPHIC; interface_graphics[IG_HEALTH_SLIDEREMPTY].type = SG_GRAPHIC; interface_graphics[IG_HEALTH_COUNT].type = SG_NUMBER; interface_graphics[IG_ARMOR_SLIDERFULL].type = SG_GRAPHIC; interface_graphics[IG_ARMOR_SLIDEREMPTY].type = SG_GRAPHIC; interface_graphics[IG_ARMOR_COUNT].type = SG_NUMBER; interface_graphics[IG_AMMO_SLIDERFULL].type = SG_GRAPHIC; interface_graphics[IG_AMMO_SLIDEREMPTY].type = SG_GRAPHIC; interface_graphics[IG_AMMO_COUNT].type = SG_NUMBER; trap_S_StartLocalSound( cgs.media.interfaceSnd1, CHAN_LOCAL_SOUND ); cg.interfaceStartupDone = 1; // All done } interface_graphics[IG_GROW].timer = cg.time + 10; } cg.interfaceStartupTime = cg.time; // kef -- init struct for post game awards InitPostGameMenuStruct(); } /* =========================================================================================== UPPER RIGHT CORNER =========================================================================================== */ /* ================== CG_DrawSnapshot ================== */ static float CG_DrawSnapshot( float y ) { char *s; int32_t w; s = va( "time:%i frametime:%i snap:%i cmd:%i", cg.snap->serverTime, cg.frametime, cg.latestSnapshotNum, cgs.serverCommandSequence ); w = UI_ProportionalStringWidth(s,UI_BIGFONT); if ( cg_lagometer.integer && ( y < (BIGCHAR_HEIGHT * 2) + 20) ) { w = w + 52; } UI_DrawProportionalString(635 - (w - 2), y + 2, s, UI_BIGFONT, colorTable[CT_LTGOLD1]); return y + BIGCHAR_HEIGHT + 10; } /* ================== CG_DrawFPS ================== */ #define FPS_FRAMES 4 static float CG_DrawFPS( float y ) { char *s; static int32_t previousTimes[FPS_FRAMES]; static int32_t index; static int32_t previous; int32_t 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 int32_t total = 0, i; for ( i = 0 ; i < FPS_FRAMES ; i++ ) { total += previousTimes[i]; } if ( !total ) { total = 1; } int32_t fps = 1000 * FPS_FRAMES / total; s = va( "%ifps", fps ); int32_t w = UI_ProportionalStringWidth(s,UI_BIGFONT); //RPG-X | Phenix | 08/06/2005 // Changed "- w" to "- (w + 50)" to account for lagometer if ( !cg_lagometer.integer ) { w = w - 52; } UI_DrawProportionalString(635 - (w + 52), y + 2, s, UI_BIGFONT, colorTable[CT_LTGOLD1]); } return y + BIGCHAR_HEIGHT + 10; } /* ================= CG_DrawTimer ================= */ static float CG_DrawTimer( float y ) { char *s; int32_t w; int32_t mins, seconds, tens; int32_t 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 = UI_ProportionalStringWidth(s,UI_BIGFONT); // RPG-X | Phenix | 08/06/2005 // Changed "- w" to "- (w + 50)" to account for lagometer if ( !cg_lagometer.integer ) { w = w - 52; } UI_DrawProportionalString(635 - (w + 52), y + 2, s, UI_BIGFONT, colorTable[CT_LTGOLD1]); return y + BIGCHAR_HEIGHT + 10; } static const double TINYPAD = 1.25; /* ===================== CG_DrawUpperRight ===================== */ static void CG_DrawUpperRight( void ) { float y; cgs.widescreen.state = WIDESCREEN_RIGHT; y = 0; if ( cg_drawFPS.integer ) { y = CG_DrawFPS( y ); } if ( cg_drawTimer.integer ) { y = CG_DrawTimer( y ); } if ( cg_drawSnapshot.integer ) { y = CG_DrawSnapshot( y ); } cgs.widescreen.state = WIDESCREEN_NONE; } /* =========================================================================================== LOWER RIGHT CORNER =========================================================================================== */ /* ================= CG_DrawScores Draw the small two score display ================= */ static float CG_DrawScores( float y ) { float y1; y -= BIGCHAR_HEIGHT + 8; y1 = y; return y1 - 8; } /* ================ CG_DrawPowerups ================ */ static float CG_DrawPowerups( float y ) { int32_t sorted[MAX_POWERUPS]; int32_t sortedTime[MAX_POWERUPS]; int32_t i, j, k; int32_t active; playerState_t *ps; int32_t t; gitem_t *item; int32_t x; int32_t color; float size; float f; static float colors[2][4] = { { 0.2, 1.0, 0.2, 1.0 } , { 1.0, 0.2, 0.2, 1.0 } }; int32_t hasHoldable; hasHoldable = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; ps = &cg.snap->ps; if ( ps->stats[STAT_HEALTH] <= 0 ) { return y; } // sort the list by time remaining active = 0; for ( i = 0 ; i < MAX_POWERUPS ; i++ ) { if ( !ps->powerups[ i ] ) { continue; } t = ps->powerups[ i ] - cg.time; // ZOID--don't draw if the power up has unlimited time (999 seconds) // This is true of the CTF flags if ( t < 0 || t > 999000) { continue; } // insert into the list for ( j = 0 ; j < active ; j++ ) { if ( sortedTime[j] >= t ) { for ( k = active - 1 ; k >= j ; k-- ) { sorted[k+1] = sorted[k]; sortedTime[k+1] = sortedTime[k]; } break; } } sorted[j] = i; sortedTime[j] = t; active++; } // draw the icons and timers x = 648; for ( i = 0 ; i < active ; i++ ) { // Don't draw almost timed out powerups if we have more than 3 and a holdable item if (!(hasHoldable && ipowerups[ sorted[i] ]; if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) { trap_R_SetColor( NULL ); } else { vec4_t modulate; f = (float)( t - cg.time ) / POWERUP_BLINK_TIME; f -= (int32_t)f; modulate[0] = modulate[1] = modulate[2] = modulate[3] = f; trap_R_SetColor( modulate ); } if ( cg.powerupActive == sorted[i] && cg.time - cg.powerupTime < PULSE_TIME ) { f = 1.0 - ( ( (float)cg.time - cg.powerupTime ) / PULSE_TIME ); size = ICON_SIZE * ( 1.0 + ( PULSE_SCALE - 1.0 ) * f ); } else { size = ICON_SIZE; } x -= size + 10; CG_DrawPic( x, 478 - size, size, size, trap_R_RegisterShader( item->icon ) ); } } trap_R_SetColor( NULL ); return y; } /* ===================== CG_DrawLowerRight ===================== */ static void CG_DrawLowerRight( void ) { float y; y = LOWEROVERLAY_Y; cgs.widescreen.state = WIDESCREEN_RIGHT; y = CG_DrawScores( y ); y = CG_DrawPowerups( y ); cgs.widescreen.state = WIDESCREEN_NONE; } /* =================== CG_DrawPickupItem =================== */ static int32_t CG_DrawPickupItem( int32_t y ) { int32_t value; float *fadeColor; if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) { return y; } y -= ICON_SIZE; value = cg.itemPickup; if ( value ) { fadeColor = CG_FadeColor( cg.itemPickupTime, 3000 ); if ( fadeColor ) { CG_RegisterItemVisuals( value ); trap_R_SetColor( fadeColor ); CG_DrawPic( 8, y, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); UI_DrawProportionalString( ICON_SIZE + 16, y + (ICON_SIZE/2 - BIGCHAR_HEIGHT/2), bg_itemlist[ value ].pickup_name, UI_SMALLFONT, fadeColor); trap_R_SetColor( NULL ); } } return y; } /* ===================== CG_DrawLowerLeft ===================== */ static void CG_DrawLowerLeft( void ) { float y; y = LOWEROVERLAY_Y; cgs.widescreen.state = WIDESCREEN_LEFT; y = CG_DrawPickupItem( y ); cgs.widescreen.state = WIDESCREEN_NONE; } //=========================================================================================== /* =================== CG_DrawHoldableItem =================== */ static void CG_DrawHoldableItem( void ) { int32_t value; value = cg.snap->ps.stats[STAT_HOLDABLE_ITEM]; if ( value ) { CG_RegisterItemVisuals( value ); if ( cg.snap->ps.stats[STAT_USEABLE_PLACED] && cg.snap->ps.stats[STAT_USEABLE_PLACED] != 2 ) {//draw detpack... Borg 2-part teleporter will just draw the same until done CG_DrawPic( 640-ICON_SIZE, 480-ICON_SIZE, ICON_SIZE, ICON_SIZE, cgs.media.detpackPlacedIcon ); } else { CG_DrawPic( 640-ICON_SIZE, 480-ICON_SIZE, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon ); } } else {//holding nothing... if ( cg.snap->ps.stats[STAT_USEABLE_PLACED] > 0 ) {//it's a timed countdown to getting a holdable, display the number in seconds int32_t sec; char *s; int32_t w; sec = cg.snap->ps.stats[STAT_USEABLE_PLACED]; if ( sec < 0 ) { sec = 0; } s = va( "%i", sec ); w = UI_ProportionalStringWidth(s,UI_BIGFONT); UI_DrawProportionalString(640-(ICON_SIZE/2)-(w/2), (SCREEN_HEIGHT-ICON_SIZE)/2+(BIGCHAR_HEIGHT/2), s, UI_BIGFONT, colorTable[CT_WHITE]); } } } /* =================== CG_DrawReward =================== */ static void CG_DrawReward( void ) { float *color; int32_t i; float x, y; if ( !cg_drawRewards.integer ) { return; } color = CG_FadeColor( cg.rewardTime, REWARD_TIME ); if ( !color ) { return; } trap_R_SetColor( color ); y = 56; x = 320 - cg.rewardCount * ICON_SIZE/2; for ( i = 0 ; i < cg.rewardCount ; i++ ) { CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader ); x += ICON_SIZE; } trap_R_SetColor( NULL ); } /* =============================================================================== LAGOMETER =============================================================================== */ #define LAG_SAMPLES 128 typedef struct { int32_t frameSamples[LAG_SAMPLES]; int32_t frameCount; int32_t snapshotFlags[LAG_SAMPLES]; int32_t snapshotSamples[LAG_SAMPLES]; int32_t snapshotCount; } lagometer_t; lagometer_t lagometer; /* ============== CG_AddLagometerFrameInfo Adds the current interpolate / extrapolate bar for this frame ============== */ void CG_AddLagometerFrameInfo( void ) { int32_t 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; int32_t cmdNum; usercmd_t cmd; const char *s; int32_t w; // 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 ) { return; } // also add text in center of screen s = ingame_text[IGT_CONNECTIONINTERRUPTED]; w = UI_ProportionalStringWidth(s,UI_BIGFONT); // Used to be (Height) 100 UI_DrawProportionalString(320 - w/2, 240, s, UI_BIGFONT, colorTable[CT_LTGOLD1]); // blink the icon if ( ( cg.time >> 9 ) & 1 ) { return; } // RPG-X | Phenix | 08/06/2005 x = 296; //640 - 50; y = 182; 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 ) { int32_t a, x, y, i; float v; float ax, ay, aw, ah, mid, range; int32_t color; float vscale; if ( !cg_lagometer.integer ) { CG_DrawDisconnect(); return; } // // draw the graph // // 640, 480 (-48) x = 640 - 50; //move it left of the ammo numbers y = 2; trap_R_SetColor( NULL ); CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader ); ax = x; ay = y; aw = 48; ah = 48; CG_AdjustFrom640( &ax, &ay, &aw, &ah ); 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 ); UI_DrawProportionalString(ax, ay, "snc", UI_BIGFONT, colorTable[CT_LTGOLD1]); } CG_DrawDisconnect(); } /* =============================================================================== CENTER PRINTING =============================================================================== */ /* ================= CG_DrawSelfdestructTimer ================= */ static float CG_DrawSelfdestructTimer( void ) { char *s; int32_t msec = cg.selfdestructTime - cg.time; if (msec > 0){ int32_t mins = msec / 60000; int32_t tens = (msec - (mins * 60000)) / 10000; int32_t seconds = (msec - (mins * 60000) - (tens * 10000)) / 1000; int32_t remainder = msec - (mins * 60000) - (tens * 10000) - (seconds * 1000); s = va( "%i:%i%i.%i", mins, tens, seconds, remainder ); int32_t w = UI_ProportionalStringWidth("SELF-DESTRUCT IN",UI_SMALLFONT); UI_DrawProportionalString(320 - (w / 2), 10, "SELF-DESTRUCT IN", UI_SMALLFONT, colorTable[CT_RED]); w = UI_ProportionalStringWidth(s,UI_SMALLFONT); UI_DrawProportionalString(320 - (w / 2), 30, s, UI_SMALLFONT, colorTable[CT_RED]); if((10000 - 0.5 * cg.frametime) < msec && msec < (10000 + 0.5 * cg.frametime)) trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( "sound/voice/computer/voy3/10sec.mp3" ) ); if((9000 - 0.5 * cg.frametime) < msec && msec < (9000 + 0.5 * cg.frametime)) trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( "sound/voice/computer/voy3/9sec.mp3" ) ); if((8000 - 0.5 * cg.frametime) < msec && msec < (8000 + 0.5 * cg.frametime)) trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( "sound/voice/computer/voy3/8sec.mp3" ) ); if((7000 - 0.5 * cg.frametime) < msec && msec < (7000 + 0.5 * cg.frametime)) trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( "sound/voice/computer/voy3/7sec.mp3" ) ); if((6000 - 0.5 * cg.frametime) < msec && msec < (6000 + 0.5 * cg.frametime)) trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( "sound/voice/computer/voy3/6sec.mp3" ) ); if((5000 - 0.5 * cg.frametime) < msec && msec < (5000 + 0.5 * cg.frametime)) trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( "sound/voice/computer/voy3/5sec.mp3" ) ); if((4000 - 0.5 * cg.frametime) < msec && msec < (4000 + 0.5 * cg.frametime)) trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( "sound/voice/computer/voy3/4sec.mp3" ) ); if((3000 - 0.5 * cg.frametime) < msec && msec < (3000 + 0.5 * cg.frametime)) trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( "sound/voice/computer/voy3/3sec.mp3" ) ); if((2000 - 0.5 * cg.frametime) < msec && msec < (2000 + 0.5 * cg.frametime)) trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( "sound/voice/computer/voy3/2sec.mp3" ) ); if((1000 - 0.5 * cg.frametime) < msec && msec < (1000 + 0.5 * cg.frametime)) trap_S_StartSound (NULL, cg.snap->ps.clientNum, CHAN_AUTO, trap_S_RegisterSound( "sound/voice/computer/voy3/1sec.mp3" ) ); } return 0; } /* ============== 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, int32_t y, int32_t 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; int32_t l; int32_t x, y, w; float *color; 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 < 60; l++ ) { if ( !start[l] || start[l] == '\n' ) { break; } linebuffer[l] = start[l]; } linebuffer[l] = 0; w = UI_ProportionalStringWidth(linebuffer,UI_BIGFONT); x = ( SCREEN_WIDTH - w ) / 2; UI_DrawProportionalString( x, y, linebuffer, UI_BIGFONT|UI_DROPSHADOW, color); y += cg.centerPrintCharWidth * 1.5; while ( *start && ( *start != '\n' ) ) { start++; } if ( !*start ) { break; } start++; } trap_R_SetColor( NULL ); } /* ================= CG_DrawCrosshair ================= */ static void CG_DrawCrosshair(void) { float w, h; float f; float x = 0; float y = 0; //float int32_t weaponCrosshairNum; trace_t trace; vec3_t start, end; int32_t ignore; vec3_t d_f; vec3_t pitchConstraint; vec3_t worldPoint; crosshairsData_t *cd; if( cg.zoomed ) { //RPG-X - TiM: We dun need crosshairs when zoomed anymore :P return; } if ( !cg_drawCrosshair.integer ) { return; } if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { return; } //clamp crosshair num if ( (weaponCrosshairNum = cg.predictedPlayerState.weapon - 1) < 0 ){ weaponCrosshairNum = 0; } else if ( weaponCrosshairNum >= MAX_CROSSHAIRS ) { weaponCrosshairNum = 14; } cd = &cgs.crosshairsData[weaponCrosshairNum]; ignore = cg.predictedPlayerState.clientNum; //if noDraw was specified in the crosshair script if ( cd->noDraw ) { return; } 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 ); } //dCross if( cg_dynamicCrosshair.value == 1 && cg.renderingThirdPerson) { VectorCopy( cg.predictedPlayerState.viewangles, pitchConstraint); //cg.predictedPlayerState.viewangles //cg.refdefViewAngles //vieworg AngleVectors( pitchConstraint, d_f, NULL, NULL ); VectorCopy( cg.predictedPlayerState.origin, start); if ( !(cg.predictedPlayerState.eFlags & EF_FULL_ROTATE) && Q_fabs( cg.predictedPlayerState.viewangles[PITCH] ) > 89.9f ) start[2] -= 20; else start[2] += (float)cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height; VectorMA( start, 6000.0f, d_f, end ); //cg.distanceCull CG_Trace( &trace, start, vec3_origin, vec3_origin, end, ignore, CONTENTS_SOLID|CONTENTS_BODY ); //TiM - if we hit a cloaked admin, bypass them so the crosshair doesn't jump randomly //NOTE: Possibly could cause errors while ( cg_entities[trace.entityNum].currentState.powerups & ( 1 <ps.clientNum].isAdmin && cg.crosshairClientNum < ENTITYNUM_WORLD ) { vec4_t ccolor; ccolor[0] = 0.694f; //0.9F;//R ccolor[1] = 0.816f; //0.7F;//G ccolor[2] = 1.0f; //0.0F;//B ccolor[3] = 0.8f; //TiM cgs.widescreen.state = WIDESCREEN_CENTER; UI_DrawProportionalString(x + 320, y + 270, va("Entity: %i", cg.crosshairClientNum), UI_CENTER|UI_SMALLFONT, ccolor); //170 } } cgs.widescreen.state = WIDESCREEN_LEFT; CG_AdjustFrom640( &x, &y, &w, &h ); trap_R_SetColor( cd->color ); //TiM: Huh... we have a problem cap'n. //Even though we have absolutely perfect alignment, the ingame drawing (regardless of mipmapping) //appears to be blurring the icons to the point where they overlap, leaving little smudges at the corners //of certain crosshairs ingame :'( //So I'm attempting to fix this by creating a very very subtle offset to scale the scan region inwards a bit. //Addendum: FRAK! Okay... offsetting will not work. It clips any of the hairs that are in their full boundary. Which looks crap :P //Magic number! 0.0078125 = 1 pixel in a 128x128 bitmap - Edited out. 1 pixel = WAY TOO MUCH! trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (cg.refdef.width - w), //X y + cg.refdef.y + 0.5 * (cg.refdef.height - h), //Y w, h, //W+H ((float)cd->s1/128.0f), ((float)cd->t1/128.0f), //s1 + t1 ((float)cd->s2/128.0f), ((float)cd->t2/128.0f), //s2 + t2 cgs.media.crosshairSheet ); trap_R_SetColor( NULL ); } /* ================= CG_LabelCrosshairEntity ================= */ static void CG_LabelViewEntity( int32_t clientNum, vec3_t origin, vec3_t entMins, vec3_t entMaxs, char *name, qboolean scanAll, vec4_t color, qboolean drawHealth, int32_t health, char *pClass, char *rank, char *race, char* age, char *height, char *weight, char *weapon ) {//ID teammates, ID enemies, ID objectives, etc. vec3_t center, maxs, mins, top, bottom, topLeft, topRight, bottomLeft, bottomRight; vec3_t worldEast = {1.0f, 0, 0}, worldNorth = {0, 1.0f, 0}, worldUp = {0, 0, 1.0f}; float x = 0, y = 0; float topLeftx, topLefty, topRightx, topRighty, bottomLeftx, bottomLefty, bottomRightx, bottomRighty; int32_t corner, topSize, bottomSize, leftSize, rightSize; int32_t charIndex, classCharIndex, rankCharIndex, ageCharIndex, raceCharIndex, htCharIndex, wtCharIndex, weapCharIndex, healthCharIndex; float lineHorzLength = 8.0f, lineVertLength = 8.0f, lineWidth = 2.0f; float fUpDot, fEastDot, fNorthDot, uNorthDot, uEastDot; qboolean doTopLeft = qfalse; qboolean doTopRight = qfalse; qboolean doBottomLeft = qfalse; qboolean doBottomRight = qfalse; qboolean doSizes = qtrue; float w; char showName[1024]; char showRank[1024]; char showRace[1024]; char showHt[1024]; char showWt[1024]; char showWeap[1024]; char showHealth[1024]; char showAge[1024]; char showClass[1024]; infoStringCount += cg.frametime; rankCharIndex = raceCharIndex = classCharIndex = ageCharIndex = htCharIndex = wtCharIndex = weapCharIndex = charIndex = healthCharIndex = floor(infoStringCount/33); //TODO: have box scale in from corners of screen? Or out from center? //IDEA: We COULD actually rotate a wire-mesh version of the crossEnt until it // matches the crossEnt's angles then flash it and pop up this info... // but that would be way too much work for something like this. // Alternately, could rotate a scaled-down fully-skinned version // next to it, but that, too, might be overkill... (plus, model would // need back faces) //FIXME: can be optimized... //Draw frame around ent's bbox //FIXME: make global, do once fUpDot = 1.0f - fabs( DotProduct( vfwd_n, worldUp ) ); //1.0 if looking up or down, so use mins and maxs more fEastDot = fabs( DotProduct( vfwd_n, worldEast ) ); //1.0 if looking east or west, so use mins[1] and maxs[1] more fNorthDot = fabs( DotProduct( vfwd_n, worldNorth ) ); //1.0 if looking north or south, so use mins[0] and maxs[0] more uEastDot = fabs( DotProduct( vup_n, worldEast ) ); //1.0 if looking up or down, head towards east or west, so use mins[0] and maxs[0] more uNorthDot = fabs( DotProduct( vup_n, worldNorth ) ); //1.0 if looking up or down, head towards north or south, so use mins[1] and maxs[1] more VectorCopy( origin, center ); //crossEnt->currentOrigin//cent->lerpOrigin VectorCopy( entMaxs, maxs ); //crossEnt->maxs //playerMaxs VectorCopy( entMins, mins ); //crossEnt->mins //playerMins //NOTE: this presumes that mins[0] and maxs[0] are symmetrical and mins[1] and maxs[1] as well topSize = (maxs[2]*fUpDot + maxs[1]*uNorthDot + maxs[0]*uEastDot);//* timedScale bottomSize = (mins[2]*fUpDot + mins[1]*uNorthDot + mins[0]*uEastDot);//* timedScale leftSize = (fUpDot*(mins[0]*fNorthDot + mins[1]*fEastDot) + mins[0]*uNorthDot + mins[1]*uEastDot);//* timedScale rightSize = (fUpDot*(maxs[0]*fNorthDot + maxs[1]*fEastDot) + maxs[0]*uNorthDot + maxs[1]*uEastDot);//* timedScale //Find corners //top VectorMA( center, topSize, vup_n, top ); //bottom VectorMA( center, bottomSize, vup_n, bottom ); //Top-left frame VectorMA( top, leftSize, vright_n, topLeft ); //Top-right frame VectorMA( top, rightSize, vright_n, topRight ); //bottom-left frame VectorMA( bottom, leftSize, vright_n, bottomLeft ); //bottom-right frame VectorMA( bottom, rightSize, vright_n, bottomRight ); if ( CG_WorldCoordToScreenCoord( topLeft, &topLeftx, &topLefty, qfalse ) ) { doTopLeft = qtrue; } else { doSizes = qfalse; } if ( CG_WorldCoordToScreenCoord( topRight, &topRightx, &topRighty, qfalse ) ) { doTopRight = qtrue; } else { doSizes = qfalse; } if ( CG_WorldCoordToScreenCoord( bottomLeft, &bottomLeftx, &bottomLefty, qfalse ) ) { doBottomLeft = qtrue; } else { doSizes = qfalse; } if ( CG_WorldCoordToScreenCoord( bottomRight, &bottomRightx, &bottomRighty, qfalse ) ) { doBottomRight = qtrue; } else { doSizes = qfalse; } //NOTE: maybe print color-coded "Primary/Secondary Objective" on top if an objective? for ( corner = 0; corner < 13; corner++ ) //11 {//FIXME: make sure line length of 8 isn't greater than width of object switch ( corner ) { case 0://top-left if ( doTopLeft ) { if ( doSizes ) { //Line lengths lineVertLength = (bottomLefty-topLefty)*0.25f; lineHorzLength = (topRightx-topLeftx)*0.25f; } CG_FillRect( topLeftx + 2, topLefty, lineHorzLength, lineWidth, color ); CG_FillRect( topLeftx, topLefty, lineWidth, lineVertLength, color ); } break; case 1://top-right if ( doTopRight ) { if ( doSizes ) { //Line lengths lineVertLength = (bottomRighty-topRighty)*0.25f; lineHorzLength = (topRightx-topLeftx)*0.25f; } CG_FillRect( topRightx-lineHorzLength, topRighty, lineHorzLength, lineWidth, color ); CG_FillRect( topRightx, topRighty, lineWidth, lineVertLength, color ); } break; case 2://bottom-left if ( doBottomLeft ) { if ( doSizes ) { //Line lengths lineVertLength = (bottomLefty-topLefty)*0.25f; lineHorzLength = (bottomRightx-bottomLeftx)*0.25f; } CG_FillRect( bottomLeftx, bottomLefty, lineHorzLength, lineWidth, color ); CG_FillRect( bottomLeftx, bottomLefty-lineVertLength, lineWidth, lineVertLength, color ); } break; case 3://bottom-right if ( doBottomRight ) { if ( doSizes ) { //Line lengths lineVertLength = (bottomRighty-topRighty)*0.25f; lineHorzLength = (bottomRightx-bottomLeftx)*0.25f; } CG_FillRect( bottomRightx-lineHorzLength, bottomRighty, lineHorzLength, lineWidth, color ); CG_FillRect( bottomRightx, bottomRighty-lineVertLength, lineWidth, lineVertLength + 2, color ); } break; case 4://healthBar if ( charIndex > 0 ) { //try to draw at top as to not obscure the tricorder CG_WorldCoordToScreenCoord( top, &x, &y, qtrue ); if ( y > 0.01 ) { y -= SMALLCHAR_HEIGHT; if ( y > 0.01 ) { if ( charIndex > 0 && name ) { if ( y >= SMALLCHAR_HEIGHT ) { y -= SMALLCHAR_HEIGHT; } else { y = 0.01; } } if ( y > 0.01 ) { if ( rankCharIndex > 0 && rank ) { if ( y >= SMALLCHAR_HEIGHT ) { y -= SMALLCHAR_HEIGHT; } } if ( y > 0.01 ) { if ( ageCharIndex > 0 && age ) { if ( y >= SMALLCHAR_HEIGHT ) { y -= SMALLCHAR_HEIGHT; } } if ( y > 0.01 ) { if ( classCharIndex > 0 && pClass ) { if ( y >= SMALLCHAR_HEIGHT ) { y -= SMALLCHAR_HEIGHT; } } if ( y > 0.01 ) { if ( raceCharIndex > 0 && race ) { if ( y >= SMALLCHAR_HEIGHT ) { y -= SMALLCHAR_HEIGHT; } } if ( y > 0.01 ) { if ( htCharIndex > 0 && height ) { if ( y >= SMALLCHAR_HEIGHT ) { y -= SMALLCHAR_HEIGHT; } } if ( y > 0.01 ) { if ( wtCharIndex > 0 && weight ) { if ( y >= SMALLCHAR_HEIGHT ) { y -= SMALLCHAR_HEIGHT; } } if ( y > 0.01 ) { if ( weapCharIndex > 0 && weapon ) { if ( y >= SMALLCHAR_HEIGHT ) { y -= SMALLCHAR_HEIGHT; } } } } } } } } } } } if ( !color[0] && !color[1] && !color[2] ) { // We really don't want black, so set it to yellow color[0] = 0.9F;//R color[1] = 0.7F;//G color[2] = 0.0F;//B } color[3] = 0.75; if ( !drawHealth || !health ) { continue; } Com_sprintf( showHealth, sizeof( showHealth ), "%s: %i", "Health", health ); if ( healthCharIndex > 0 && showHealth[0] ) { int32_t len = strlen( showHealth ); if ( healthCharIndex > len+1 ) { healthCharIndex = len+1; } else { trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); } w = CG_DrawStrlen( showHealth ) * SMALLCHAR_WIDTH; Q_strncpyz( showHealth, showHealth, healthCharIndex ); CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showHealth, color ); y += SMALLCHAR_HEIGHT; } } break; case 5: if ( charIndex > 0 && name ) { int32_t len = strlen(name); if ( charIndex > len+1 ) { charIndex = len+1; } else { trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); } Q_strncpyz( showName, name, charIndex ); w = CG_DrawStrlen( name ) * SMALLCHAR_WIDTH; CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showName, color ); y += SMALLCHAR_HEIGHT; } break; case 6://class if ( classCharIndex > 0 && pClass ) { int32_t len = strlen(pClass); if ( classCharIndex > len+1 ) { classCharIndex = len+1; } else { trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); } Q_strncpyz( showClass, pClass, classCharIndex ); w = CG_DrawStrlen( pClass ) * SMALLCHAR_WIDTH; CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showClass, color ); y += SMALLCHAR_HEIGHT; } break; case 7://rank if ( rankCharIndex > 0 && rank ) { int32_t len = strlen(rank); if ( rankCharIndex > len+1 ) { rankCharIndex = len+1; } else { trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); } Q_strncpyz( showRank, rank, rankCharIndex ); w = CG_DrawStrlen( rank ) * SMALLCHAR_WIDTH; CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showRank, color ); y += SMALLCHAR_HEIGHT; } break; case 8://age if ( ageCharIndex > 0 && age ) { int32_t len = strlen(age); if ( ageCharIndex > len+1 ) { ageCharIndex = len+1; } else { trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); } Q_strncpyz( showAge, age, ageCharIndex ); w = CG_DrawStrlen( age ) * SMALLCHAR_WIDTH; CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showAge, color ); y += SMALLCHAR_HEIGHT; } break; case 9://race if ( raceCharIndex > 0 && race ) { int32_t len = strlen(race); if ( raceCharIndex > len+1 ) { raceCharIndex = len+1; } else { trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); } Q_strncpyz( showRace, race, raceCharIndex ); w = CG_DrawStrlen( race ) * SMALLCHAR_WIDTH; CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showRace, color ); y += SMALLCHAR_HEIGHT; } break; case 10://height if ( htCharIndex > 0 && height ) { int32_t len = strlen(height); if ( htCharIndex > len+1 ) { htCharIndex = len+1; } else { trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); } Q_strncpyz( showHt, height, htCharIndex ); w = CG_DrawStrlen( height ) * SMALLCHAR_WIDTH; CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showHt, color ); y += SMALLCHAR_HEIGHT; } break; case 11://weight if ( wtCharIndex > 0 && weight ) { int32_t len = strlen(weight); if ( wtCharIndex > len+1 ) { wtCharIndex = len+1; } else { trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); } Q_strncpyz( showWt, weight, wtCharIndex ); w = CG_DrawStrlen( weight ) * SMALLCHAR_WIDTH; CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showWt, color ); y += SMALLCHAR_HEIGHT; } break; case 12://weapon if ( weapCharIndex > 0 && weapon ) { int32_t len = strlen(weapon); if ( weapCharIndex > len+1 ) { weapCharIndex = len+1; } else { trap_S_StartSound( NULL, 0, CHAN_ITEM, cgs.media.tedTextSound ); } Q_strncpyz( showWeap, weapon, weapCharIndex ); w = CG_DrawStrlen( weapon ) * SMALLCHAR_WIDTH; CG_DrawSmallStringColor( x - w / 2, y + lineWidth, showWeap, color ); y += SMALLCHAR_HEIGHT; } break; } } } /* ================= CG_ScanForCrosshairEntity ================= */ static void CG_ScanForCrosshairEntity( void ) { trace_t trace; vec3_t start, end; vec3_t pitchConstraint, df_f; VectorCopy( cg.predictedPlayerState.origin, start ); //cg.refdef.vieworg start[2] += (float)cg.predictedPlayerState.viewheight * cgs.clientinfo[cg.predictedPlayerState.clientNum].height; VectorCopy( cg.predictedPlayerState.viewangles, pitchConstraint ); AngleVectors( pitchConstraint, df_f, NULL, NULL ); VectorMA( start, 8912, df_f, end); if ( cg.snap->ps.weapon == WP_7 && cg.zoomed ) { CG_Trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, CONTENTS_BODY ); // if the player is invisible, don't show it if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) && !cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.snap->ps.persistant[PERS_CLASS] != PC_ADMIN*/ ) { return; } } else { CG_Trace( &trace, start, vec3_origin, vec3_origin, end, cg.snap->ps.clientNum, MASK_SHOT ); //CONTENTS_SOLID|CONTENTS_BODY //This was causing Scanables for non-admins to be completely unscanable. I'm not sure about the legacy, so I'm keeping this in. //if ( trace.entityNum >= MAX_CLIENTS && !cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.predictedPlayerState.persistant[PERS_CLASS] != PC_ADMIN*/ ) { // return; //} // if the player is in fog, don't show it int32_t content = trap_CM_PointContents( trace.endpos, 0 ); if ( content & CONTENTS_FOG ) { return; } // if the player is invisible, don't show it if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_INVIS ) && !cgs.clientinfo[cg.snap->ps.clientNum].isAdmin/*cg.snap->ps.persistant[PERS_CLASS] != PC_ADMIN*/ ) { return; } if ( cg.crosshairClientNum != trace.entityNum) { infoStringCount = 0; } } // update the fade timer cg.crosshairClientNum = trace.entityNum; cg.crosshairClientTime = cg.time; } /* ===================== CG_DrawCrosshairNames ===================== */ extern qboolean PM_PlayerCrouching ( int32_t legsAnim ); static vec3_t playerMins = {-12, -12, -24}; //RPG-X : TiM - {-15, -15, -24} static vec3_t playerMaxs = {12, 12, 32}; // {15, 15, 32} static void CG_DrawCrosshairNames( void ) { float *color; char name[MAX_QPATH]; centity_t *cent; int32_t x, y; qboolean tinyFont; int32_t drawFlags; if ( !cg_drawCrosshair.integer ) { return; } // scan the known entities to see if the crosshair is sighted on one CG_ScanForCrosshairEntity(); // draw the name of the player being looked at color = CG_FadeColor( cg.crosshairClientTime, 1000 ); if ( !color ) { trap_R_SetColor( NULL ); infoStringCount = 0; return; } color[3] *= 0.9; //If they're actively firing the tricorder if( ( (cg.snap->ps.eFlags & EF_FIRING) && !(cg.snap->ps.eFlags & EF_ALT_FIRING) ) && cg.snap->ps.weapon == WP_2 ) { if(cg.crosshairClientNum != cg.predictedPlayerState.clientNum && cg.crosshairClientNum < MAX_CLIENTS ) { //ENTITYNUM_WORLD drawCrosshairName = qfalse; cent = &cg_entities[cg.crosshairClientNum]; if ( cent ) { char *name = NULL; char *rank = NULL; char *race = NULL; char *age = NULL; char *pClass = NULL; float ht = 0; float wt = 0; char *weap = NULL; char namestr[128]; char rankstr[128]; char racestr[128]; char htstr[128]; char wtstr[128]; char weapstr[128]; char agestr[128]; char classstr[128]; int32_t i, irank; int32_t score = 0; clientInfo_t *ci; for ( i = 0; i < cgs.maxclients; i++ ) { if ( cg.scores[i].client == cg.crosshairClientNum ) { score = cg.scores[i].score; break; } } irank = score; //Q_log2( score ); ci = &cgs.clientinfo[cg.crosshairClientNum]; //over-ride the color, since we can't get teams in this case //use that good old LCARS yellow color[0] = 0.694f; //0.9F;//R color[1] = 0.816f; //0.7F;//G color[2] = 1.0f; //0.0F;//B color[3] *= 0.5; ht = ci->height * (float)BASE_HEIGHT; wt = ci->weight * ci->height * (float)BASE_WEIGHT; if ( ci->gender == GENDER_FEMALE ) { wt *= (float)FEMALE_OFFSET; //magic number, women are lighter than men } if ( ci->race && ci->race[0] ) { race = ci->race; Com_sprintf( racestr, sizeof( racestr ), "%s: %s", "Race", race ); } if ( ci->age && ci->age[0] ) { age = ci->age; Com_sprintf( agestr, sizeof( agestr ), "%s: %s", "Age", age ); } pClass = cgs.classData[ci->pClass].formalName; if ( pClass ) { Com_sprintf( classstr, sizeof(classstr), "%s: %s", "Class", pClass ); } if ( cgs.classData[ci->pClass].showRanks ) { if ( cgs.ranksData[irank].formalName[0] ) { rank = cgs.ranksData[irank].formalName; Com_sprintf( rankstr, sizeof( rankstr ), "%s: %s", "Rank", rank ); } } if ( ci->name && ci->name[0] ) { name = ci->name; } else { name = "Data Not Available"; //crossEnt->targetname; } Com_sprintf( namestr, sizeof( namestr), "%s: %s", "Name", name ); if ( cent->currentState.weapon != WP_1 ) { if ( cg_weapons[ cent->currentState.weapon ].item->pickup_name ) { weap = cg_weapons[ cent->currentState.weapon ].item->pickup_name; Com_sprintf( weapstr, sizeof( weapstr), "%s: %s", "Weapon", weap ); } } Com_sprintf( htstr, sizeof(htstr), "%s: %4.2f %s","Height", ht, HEIGHT_UNIT ); Com_sprintf( wtstr, sizeof(wtstr), "%s: %4.2f %s","Weight", wt, WEIGHT_UNIT ); CG_LabelViewEntity( cg.crosshairClientNum, cent->lerpOrigin, playerMins, playerMaxs, name ? namestr : NULL, qfalse, color, (cgs.clientinfo[cg.snap->ps.clientNum].isAdmin || cgs.classData[cg.snap->ps.persistant[PERS_CLASS]].isMedic) ? qtrue : qfalse, ci->health, pClass ? classstr : NULL, rank ? rankstr : NULL, race ? racestr : NULL, age ? agestr : NULL, ht ? htstr : NULL, wt ? wtstr : NULL, weap ? weapstr : NULL); } else { infoStringCount = 0; } } else { if ( (cg_entities[cg.crosshairClientNum].currentState.eType == ET_TRIC_STRING || cg_entities[cg.crosshairClientNum].currentState.eType == ET_MOVER_STR) && cgs.scannablePanels ) { entityState_t *eState; vec3_t origin; vec3_t mins, maxs; char *renderString; eState = &cg_entities[cg.crosshairClientNum].currentState; color[0] = 0.694f; //0.9F;//R color[1] = 0.816f; //0.7F;//G color[2] = 1.0f; //0.0F;//B color[3] *= 0.5; //TiM: Since dynamic brush ents seem to have no freaking origin in them, let's // calc our own using the bounding box dimensions (At least we have those lol ) VectorAverage( eState->origin2, eState->angles2, origin ); //The algorithm needs the max and min dimensions to be symmetrical on either side //of the origin. This set of random code does that. :) VectorSubtract( origin, eState->origin2, mins ); VectorSet( maxs, Q_fabs( mins[0] ), Q_fabs( mins[1] ), Q_fabs( mins[2] ) ); VectorScale( maxs, -1, mins ); if ( eState->time2 > 0 ) renderString = (char *)CG_ConfigString( CS_TRIC_STRINGS + eState->time2 ); else if ( eState->weapon > 0 && cgs.scannableStrings[eState->weapon-1][0] ) renderString = cgs.scannableStrings[eState->weapon-1]; //subtracted since '0' is a valid cell value else renderString = ""; CG_LabelViewEntity( cg.crosshairClientNum, origin, mins, maxs, renderString, qfalse, color, qfalse, 0, NULL, NULL, NULL, NULL, NULL, NULL, NULL); } } } else drawCrosshairName = qtrue; if ( !cg_drawCrosshairNames.integer || cg.crosshairClientNum > MAX_CLIENTS || !drawCrosshairName ) { return; } //Now only draw team names + health if specifically wanted Q_strncpyz (name, cgs.clientinfo[ cg.crosshairClientNum ].name, sizeof (name) ); color[0] = colorTable[CT_YELLOW][0]; color[1] = colorTable[CT_YELLOW][1]; color[2] = colorTable[CT_YELLOW][2]; if ( !cg_dynamicCrosshairNames.integer ) { x = 320; y = 170; tinyFont = qfalse; drawFlags = UI_CENTER|UI_SMALLFONT; } else { vec3_t org; centity_t *cent; float x2, y2; cent = &cg_entities[ cg.crosshairClientNum ]; VectorCopy( cent->lerpOrigin, org ); if ( PM_PlayerCrouching( cent->currentState.legsAnim ) ) org[2] += CROUCH_VIEWHEIGHT + 7; else org[2] += DEFAULT_VIEWHEIGHT + 7; CG_WorldCoordToScreenCoord( org, &x2, &y2, qfalse); x = (int32_t)x2; y = (int32_t)y2; tinyFont = qtrue; drawFlags = UI_CENTER|UI_BOTTOM|UI_TINYFONT; } //FIXME: need health (&armor?) of teammates (if not TEAM_FREE) or everyone (if SPECTATOR) (or just crosshairEnt?) sent to me if (cgs.clientinfo[ cg.snap->ps.clientNum ].team == TEAM_SPECTATOR || cgs.classData[cgs.clientinfo[ cg.snap->ps.clientNum ].pClass].isMedic || cgs.clientinfo[ cg.snap->ps.clientNum ].isAdmin ) /*|| cgs.clientinfo[ cg.snap->ps.clientNum ].pClass == PC_MEDIC*/ {//if I'm a spectator, draw colored health of target under crosshair CG_GetColorForHealth( cgs.clientinfo[ cg.crosshairClientNum ].health, cgs.clientinfo[ cg.crosshairClientNum ].armor, color ); y -= ( tinyFont ? TINYCHAR_HEIGHT : SMALLCHAR_HEIGHT ); UI_DrawProportionalString(x,y+(tinyFont ? TINYCHAR_HEIGHT+5 : SMALLCHAR_HEIGHT),va( "^7%i", cgs.clientinfo[ cg.crosshairClientNum ].health ), drawFlags,color); } UI_DrawProportionalString(x,y, va("^7%s ^7(%i)", name, cg.crosshairClientNum), drawFlags, color); } //============================================================================== /* ================= CG_DrawSpectator ================= */ static void CG_DrawSpectator(void) { if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) { UI_DrawProportionalString(SCREEN_WIDTH/2, SCREEN_HEIGHT - ((BIGCHAR_HEIGHT * 1.50) * 2) , ingame_text[IGT_SPECTATOR], UI_BIGFONT|UI_CENTER, colorTable[CT_LTGOLD1]); } } /* ================= CG_DrawVote ================= */ static void CG_DrawVote(void) { char *s; int32_t sec; 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; } s = va("%s(%i):%s %s(F1):%i %s(F2):%i", ingame_text[IGT_VOTE],sec, cgs.voteString,ingame_text[IGT_YES], cgs.voteYes,ingame_text[IGT_NO] ,cgs.voteNo); UI_DrawProportionalString( 0, 58, s, UI_SMALLFONT, colorTable[CT_YELLOW]); } /* ================= CG_DrawIntermission ================= */ static void CG_DrawIntermission( void ) { cg.scoreFadeTime = cg.time; CG_DrawScoreboard(); } /* ================= CG_DrawAbridgedObjective ================= */ static void CG_DrawAbridgedObjective(void) { int32_t i,pixelLen,x,y; for (i=0;ips.pm_flags & PMF_FOLLOW) ) { return qfalse; } color[0] = 1; color[1] = 1; color[2] = 1; color[3] = 1; y = 16; UI_DrawProportionalString((SCREEN_WIDTH/2), y, ingame_text[IGT_FOLLOWING], UI_BIGFONT|UI_CENTER, colorTable[CT_LTGOLD1]); name = cgs.clientinfo[ cg.snap->ps.clientNum ].name; y += (BIGCHAR_HEIGHT * 1.25); UI_DrawProportionalString( (SCREEN_WIDTH/2), 40, name, UI_BIGFONT|UI_CENTER, color); return qtrue; } /* ================= CG_DrawAmmoWarning ================= RPG-X | Marcin | 30/12/2008 Don't!!! */ static void CG_DrawAmmoWarning( void ) { return; } /* ================= CG_DrawWarmup ================= */ extern void CG_AddGameModNameToGameName( char *gamename ); static void CG_DrawWarmup( void ) { int32_t w; int32_t sec; const char *s; sec = cg.warmup; if ( !sec ) { return; } if ( sec < 0 ) { s = ingame_text[IGT_WAITINGFORPLAYERS]; w = UI_ProportionalStringWidth(s,UI_BIGFONT); UI_DrawProportionalString(320 - w / 2, 40, s, UI_BIGFONT, colorTable[CT_LTGOLD1]); cg.warmupCount = 0; return; } char gamename[1024]; s = ingame_text[IGT_GAME_FREEFORALL]; Q_strncpyz( gamename, s, sizeof(gamename) ); CG_AddGameModNameToGameName( gamename ); w = UI_ProportionalStringWidth(s,UI_BIGFONT); UI_DrawProportionalString((SCREEN_WIDTH/2) , 20,gamename, UI_BIGFONT|UI_CENTER, colorTable[CT_LTGOLD1]); sec = ( sec - cg.time ) / 1000; if ( sec < 0 ) { sec = 0; } s = va( "%s: %i",ingame_text[IGT_STARTSIN], 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; } } w = UI_ProportionalStringWidth(s,UI_BIGFONT); UI_DrawProportionalString( (SCREEN_WIDTH/2), 70, s, UI_BIGFONT|UI_CENTER, colorTable[CT_LTGOLD1]); } /* ================ CG_DrawZoomMask ================ */ static void CG_DrawZoomMask( void ) { float amt = 1, size, /*val,*/ start_x, start_y; int32_t width, height, i; vec4_t color1; //TiM: New system. :) Base zoom on current active weapon. :) if ( !(cg.snap->ps.weapon == WP_6 || cg.snap->ps.weapon == WP_7) ) { cg.zoomed = qfalse; cg.zoomLocked = qfalse; return; } // Calc where to place the zoom mask...all calcs are based off of a virtual 640x480 screen size = cg_viewsize.integer; width = 640 * size * 0.01; width &= ~1; height = 480 * size * 0.01; height &= ~1; start_x = ( 640 - width ) * 0.5; start_y = ( 480 - height ) * 0.5; if ( cg.zoomed ) { // Smoothly fade in..Turn this off for now since the zoom is set to snap to 30% or so...fade looks a bit weird when it does that if ( cg.time - cg.zoomTime <= ZOOM_OUT_TIME ) { amt = ( cg.time - cg.zoomTime ) / ZOOM_OUT_TIME; } // Fade mask in for ( i = 0; i < 4; i++ ) { color1[i] = amt; } // Set fade color trap_R_SetColor( color1 ); if ( cg.snap->ps.weapon == WP_7 ) { static int32_t TR116LoopTime = 0; //Loop the whirring sight sound if ( TR116LoopTime < cg.time ) { trap_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_LOCAL, cgs.media.tr116Whir ); TR116LoopTime = cg.time + 900; } CG_DrawPic( start_x, start_y, width, height, cgs.media.zoomMask116Shader ); //if we're zoomed over a potential target, start flashing the red crosshair if ( cg.crosshairClientNum != cg.snap->ps.clientNum && cg.crosshairClientNum < MAX_CLIENTS ) { vec4_t alphaColor; float amt; if ( cg.time > zoomFlashTime ) { zoomFlashTime = cg.time + 800; trap_S_StartLocalSound( cgs.media.tr116Chirp, CHAN_LOCAL_SOUND ); } amt = ( ( zoomFlashTime % cg.time ) / 500.0f ); amt = Com_Clamp( 0.0, 1.0, amt ); VectorSet( alphaColor, 1, 1, 1 ); alphaColor[3] = amt; trap_R_SetColor( alphaColor ); //======================================== CG_DrawPic( 256, 176, 128, 128, cgs.media.zoomGlow116Shader ); trap_R_SetColor( color1 ); } else { zoomFlashTime = 0; } } else { CG_DrawPic( start_x, start_y, width, height, cgs.media.zoomMaskShader ); } //yellow if ( cg.snap->ps.weapon == WP_7 ) { color1[0] = 0.886f; color1[1] = 0.749f; color1[2] = 0.0f; color1[3] = 0.60f; } else { // red color1[0] = 1.0f; color1[1] = 0.0f; color1[2] = 0.0f; color1[3] = 0.60f; } // Convert zoom and view axis into some numbers to throw onto the screen int32_t x, y; if ( cg.snap->ps.weapon == WP_7 ) { x = 74; y = 340; } else { x = 468; y = 300; } trap_R_SetColor( color1 ); CG_DrawNumField( x, y, 5, cg_zoomFov.value * 1000 + 9999, 18, 10 ,NUM_FONT_BIG ); //100 CG_DrawNumField( x, y+20, 5, cg.refdef.viewaxis[0][0] * 9999 + 20000, 18, 10,NUM_FONT_BIG ); CG_DrawNumField( x, y+40, 5, cg.refdef.viewaxis[0][1] * 9999 + 20000, 18, 10,NUM_FONT_BIG ); CG_DrawNumField( x, y+60, 5, cg.refdef.viewaxis[0][2] * 9999 + 20000, 18, 10,NUM_FONT_BIG ); } else { if ( cg.time - cg.zoomTime <= ZOOM_OUT_TIME ) { amt = 1.0f - ( cg.time - cg.zoomTime ) / ZOOM_OUT_TIME; // Fade mask away for ( i = 0; i < 4; i++ ) { color1[i] = amt; } trap_R_SetColor( color1 ); if ( cg.snap->ps.weapon == WP_7 ) { CG_DrawPic( start_x, start_y, width, height, cgs.media.zoomMask116Shader ); } else { CG_DrawPic( start_x, start_y, width, height, cgs.media.zoomMaskShader ); } } } } //================================================================================== /* ===================== CG_Drawcg.adminMsg RPG-X | Phenix | 08/06/2005 RPG-X | Marcin | 30/12/2008 Now I'm going to kill you Phenix!!!! ===================== */ static void CG_DrawAdminMsg( void ) { float y; int32_t t; int32_t i, msgRow, msgCol; int32_t biggestW, w; char message[35][45]; char *thisMessage; char *p, *currRow; static vec4_t color; static vec4_t Boxcolor; y = 460; //Nothing to display if ( cg.adminMsgTime < cg.time ) { return; } //No message! if ( cg.adminMsgMsg[0] == '\0' ) { return; } //Colour Fade. t = cg.adminMsgTime - cg.time; // fade out if (t < 500) { color[3] = t * 1.0/500; } else { color[3] = 1.0; } color[0] = color[1] = color[2] = 1; Boxcolor[0] = 0.016; Boxcolor[1] = 0.055; Boxcolor[2] = 0.170; Boxcolor[3] = color[3]; trap_R_SetColor( color ); i = 0; msgRow = 0; msgCol = 0; p = cg.adminMsgMsg; currRow = p; while ( qtrue ) { if ( !*p ) { break; } if ( NextWordEndsHere( p ) - currRow > 43 ) { //we need to wrap... message[msgRow][msgCol] = '\0'; currRow = p++; ++msgRow, msgCol = 0; continue; } message[msgRow][msgCol] = *p; ++p, ++msgCol; } message[msgRow][msgCol] = '\0'; ++msgRow; biggestW = 0; for (i = 0; i < msgRow; i++) { thisMessage = va("%s", message[i]); w = UI_ProportionalStringWidth(thisMessage, UI_SMALLFONT); if (w > biggestW) { biggestW = w; } } CG_FillRect( 640 - (biggestW + 22), y - (((SMALLCHAR_HEIGHT + 2) * msgRow) + 2), biggestW + 4, ((SMALLCHAR_HEIGHT + 2) * msgRow) + 4, Boxcolor ); for (i = (msgRow - 1); i >= 0; i--) { y -= (SMALLCHAR_HEIGHT + 2); thisMessage = va("%s", message[i]); UI_DrawProportionalString(640 - (biggestW + 20), y, thisMessage, UI_SMALLFONT, color); } trap_R_SetColor( NULL ); } /* ================= CG_Draw2D ================= */ static void CG_Draw2D( void ) { int32_t i; //TiM : Testing this API function... //trap_R_SetColor( colorTable[ CT_RED ] ); //trap_R_DrawStretchPic( 100, 100, 800, 600, 0, 0, 0.5, 0.5, cgs.media.charsetPropB ); // if we are taking a levelshot for the menu, don't draw anything if ( cg.levelShot ) { return; } if ( cg_draw2D.integer == 0 ) { return; } if ( cg.snap->ps.pm_type == PM_CCAM ) { return; } if ( cg.snap->ps.pm_type == PM_INTERMISSION ) { #ifndef FINAL_BUILD CG_DrawUpperRight(); #endif CG_DrawIntermission(); return; } if ( !cg.renderingThirdPerson ) { CG_DrawZoomMask(); } //RPG-X: RedTechie - Keep Lagometer on the botum always cgs.widescreen.state = WIDESCREEN_RIGHT; CG_DrawLagometer(); cgs.widescreen.state = WIDESCREEN_NONE; if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR /*|| (cg.snap->ps.eFlags&EF_ELIMINATED)*/ ) { CG_DrawCrosshair(); CG_DrawCrosshairNames(); } else { cgs.widescreen.state = WIDESCREEN_LEFT; CG_DrawStatusBar(); //RPG-X: RedTechie - We want health displayed when dead // don't draw any status if dead if ( cg.snap->ps.stats[STAT_HEALTH] > 1 ) { //RPG-X: RedTechie - No weapons at health 1 (you die at health 1 now) CG_DrawAmmoWarning(); cgs.widescreen.state = WIDESCREEN_NONE; CG_DrawCrosshair(); cgs.widescreen.state = WIDESCREEN_CENTER; CG_DrawCrosshairNames(); cgs.widescreen.state = WIDESCREEN_LEFT; CG_DrawWeaponSelect(); cgs.widescreen.state = WIDESCREEN_RIGHT; CG_DrawHoldableItem(); CG_DrawReward(); CG_DrawAbridgedObjective(); cgs.widescreen.state = WIDESCREEN_NONE; } cgs.widescreen.state = WIDESCREEN_NONE; } if (cg.showObjectives) { CG_DrawObjectiveInformation(); } CG_DrawVote(); //RPG-X: RedTechie - Moved above others to keep on the bottum //CG_DrawLagometer(); CG_DrawUpperRight(); CG_DrawLowerRight(); CG_DrawLowerLeft(); CG_DrawSelfdestructTimer(); //RPG-X | Phenix | 08/06/2005 cgs.widescreen.state = WIDESCREEN_CENTER; CG_DrawAdminMsg(); cgs.widescreen.state = WIDESCREEN_NONE; cgs.widescreen.state = WIDESCREEN_CENTER; if ( !CG_DrawFollow() ) { CG_DrawWarmup(); } cgs.widescreen.state = WIDESCREEN_NONE; // don't draw center string if scoreboard is up cgs.widescreen.state = WIDESCREEN_CENTER; if ( !CG_DrawScoreboard() ) { CG_DrawCenterString(); } cgs.widescreen.state = WIDESCREEN_NONE; // kef -- need the "use TEAM menu to play" message to draw on top of the bottom bar of scoreboard if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR /*|| (cg.snap->ps.eFlags&EF_ELIMINATED)*/ ) { cgs.widescreen.state = WIDESCREEN_CENTER; CG_DrawSpectator(); cgs.widescreen.state = WIDESCREEN_NONE; } //TiM - Draw teh fl4r3s for (i = 0; i < MAX_LENS_FLARES; i++) { if ( lensFlare[i].qfull ) CG_DrawLensFlare( &lensFlare[i] ); if ( lensFlare[i].upTime + lensFlare[i].holdTime + lensFlare[i].downTime > 0 && cg.time > lensFlare[i].startTime + lensFlare[i].upTime + lensFlare[i].holdTime + lensFlare[i].downTime ) lensFlare[i].qfull = qfalse; } } /* ===================== 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; } //vectors needed for tricorder AngleVectors (cg.refdefViewAngles, vfwd, vright, vup); VectorCopy( vfwd, vfwd_n ); VectorCopy( vright, vright_n ); VectorCopy( vup, vup_n ); VectorNormalize( vfwd_n ); VectorNormalize( vright_n ); VectorNormalize( vup_n ); // 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(); }