mirror of
https://github.com/ioquake/jedi-academy.git
synced 2024-11-28 15:02:42 +00:00
8585 lines
199 KiB
C
8585 lines
199 KiB
C
// 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 "../game/bg_saga.h"
|
|
|
|
#include "../ui/ui_shared.h"
|
|
#include "../ui/ui_public.h"
|
|
|
|
extern float CG_RadiusForCent( centity_t *cent );
|
|
qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y);
|
|
qboolean CG_CalcMuzzlePoint( int entityNum, vec3_t muzzle );
|
|
static void CG_DrawSiegeTimer(int timeRemaining, qboolean isMyTeam);
|
|
static void CG_DrawSiegeDeathTimer( int timeRemaining );
|
|
// nmckenzie: DUEL_HEALTH
|
|
void CG_DrawDuelistHealth ( float x, float y, float w, float h, int duelist );
|
|
|
|
// used for scoreboard
|
|
extern displayContextDef_t cgDC;
|
|
menuDef_t *menuScoreboard = NULL;
|
|
vec4_t bluehudtint = {0.5, 0.5, 1.0, 1.0};
|
|
vec4_t redhudtint = {1.0, 0.5, 0.5, 1.0};
|
|
float *hudTintColor;
|
|
|
|
int sortedTeamPlayers[TEAM_MAXOVERLAY];
|
|
int numSortedTeamPlayers;
|
|
|
|
int lastvalidlockdif;
|
|
|
|
extern float zoomFov; //this has to be global client-side
|
|
|
|
char systemChat[256];
|
|
char teamChat1[256];
|
|
char teamChat2[256];
|
|
|
|
// The time at which you died and the time it will take for you to rejoin game.
|
|
int cg_siegeDeathTime = 0;
|
|
|
|
#define MAX_HUD_TICS 4
|
|
const char *armorTicName[MAX_HUD_TICS] =
|
|
{
|
|
"armor_tic1",
|
|
"armor_tic2",
|
|
"armor_tic3",
|
|
"armor_tic4",
|
|
};
|
|
|
|
const char *healthTicName[MAX_HUD_TICS] =
|
|
{
|
|
"health_tic1",
|
|
"health_tic2",
|
|
"health_tic3",
|
|
"health_tic4",
|
|
};
|
|
|
|
const char *forceTicName[MAX_HUD_TICS] =
|
|
{
|
|
"force_tic1",
|
|
"force_tic2",
|
|
"force_tic3",
|
|
"force_tic4",
|
|
};
|
|
|
|
const char *ammoTicName[MAX_HUD_TICS] =
|
|
{
|
|
"ammo_tic1",
|
|
"ammo_tic2",
|
|
"ammo_tic3",
|
|
"ammo_tic4",
|
|
};
|
|
|
|
char *showPowersName[] =
|
|
{
|
|
"HEAL2",//FP_HEAL
|
|
"JUMP2",//FP_LEVITATION
|
|
"SPEED2",//FP_SPEED
|
|
"PUSH2",//FP_PUSH
|
|
"PULL2",//FP_PULL
|
|
"MINDTRICK2",//FP_TELEPTAHY
|
|
"GRIP2",//FP_GRIP
|
|
"LIGHTNING2",//FP_LIGHTNING
|
|
"DARK_RAGE2",//FP_RAGE
|
|
"PROTECT2",//FP_PROTECT
|
|
"ABSORB2",//FP_ABSORB
|
|
"TEAM_HEAL2",//FP_TEAM_HEAL
|
|
"TEAM_REPLENISH2",//FP_TEAM_FORCE
|
|
"DRAIN2",//FP_DRAIN
|
|
"SEEING2",//FP_SEE
|
|
"SABER_OFFENSE2",//FP_SABER_OFFENSE
|
|
"SABER_DEFENSE2",//FP_SABER_DEFENSE
|
|
"SABER_THROW2",//FP_SABERTHROW
|
|
NULL
|
|
};
|
|
|
|
//Called from UI shared code. For now we'll just redirect to the normal anim load function.
|
|
#include "../namespace_begin.h"
|
|
|
|
|
|
int UI_ParseAnimationFile(const char *filename, animation_t *animset, qboolean isHumanoid)
|
|
{
|
|
return BG_ParseAnimationFile(filename, animset, isHumanoid);
|
|
}
|
|
|
|
int MenuFontToHandle(int iMenuFont)
|
|
{
|
|
switch (iMenuFont)
|
|
{
|
|
case FONT_SMALL: return cgDC.Assets.qhSmallFont;
|
|
case FONT_SMALL2: return cgDC.Assets.qhSmall2Font;
|
|
case FONT_MEDIUM: return cgDC.Assets.qhMediumFont;
|
|
case FONT_LARGE: return cgDC.Assets.qhMediumFont;//cgDC.Assets.qhBigFont;
|
|
//fixme? Big fonr isn't registered...?
|
|
}
|
|
|
|
return cgDC.Assets.qhMediumFont;
|
|
}
|
|
|
|
#include "../namespace_end.h"
|
|
|
|
int CG_Text_Width(const char *text, float scale, int iMenuFont)
|
|
{
|
|
int iFontIndex = MenuFontToHandle(iMenuFont);
|
|
|
|
return trap_R_Font_StrLenPixels(text, iFontIndex, scale);
|
|
}
|
|
|
|
int CG_Text_Height(const char *text, float scale, int iMenuFont)
|
|
{
|
|
int iFontIndex = MenuFontToHandle(iMenuFont);
|
|
|
|
return trap_R_Font_HeightPixels(iFontIndex, scale);
|
|
}
|
|
|
|
#include "../qcommon/qfiles.h" // for STYLE_BLINK etc
|
|
void CG_Text_Paint(float x, float y, float scale, vec4_t color, const char *text, float adjust, int limit, int style, int iMenuFont)
|
|
{
|
|
int iStyleOR = 0;
|
|
int iFontIndex = MenuFontToHandle(iMenuFont);
|
|
|
|
switch (style)
|
|
{
|
|
case ITEM_TEXTSTYLE_NORMAL: iStyleOR = 0;break; // JK2 normal text
|
|
case ITEM_TEXTSTYLE_BLINK: iStyleOR = STYLE_BLINK;break; // JK2 fast blinking
|
|
case ITEM_TEXTSTYLE_PULSE: iStyleOR = STYLE_BLINK;break; // JK2 slow pulsing
|
|
case ITEM_TEXTSTYLE_SHADOWED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this )
|
|
case ITEM_TEXTSTYLE_OUTLINED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this )
|
|
case ITEM_TEXTSTYLE_OUTLINESHADOWED: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this )
|
|
case ITEM_TEXTSTYLE_SHADOWEDMORE: iStyleOR = (int)STYLE_DROPSHADOW;break; // JK2 drop shadow ( need a color for this )
|
|
}
|
|
|
|
trap_R_Font_DrawString( x, // int ox
|
|
y, // int oy
|
|
text, // const char *text
|
|
color, // paletteRGBA_c c
|
|
iStyleOR | iFontIndex, // const int iFontHandle
|
|
!limit?-1:limit, // iCharLimit (-1 = none)
|
|
scale // const float scale = 1.0f
|
|
);
|
|
}
|
|
|
|
/*
|
|
qboolean CG_WorldCoordToScreenCoord(vec3_t worldCoord, int *x, int *y)
|
|
|
|
Take any world coord and convert it to a 2D virtual 640x480 screen coord
|
|
*/
|
|
/*
|
|
qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y)
|
|
{
|
|
int xcenter, ycenter;
|
|
vec3_t local, transformed;
|
|
|
|
// xcenter = cg.refdef.width / 2;//gives screen coords adjusted for resolution
|
|
// ycenter = cg.refdef.height / 2;//gives screen coords adjusted for resolution
|
|
|
|
//NOTE: did it this way because most draw functions expect virtual 640x480 coords
|
|
// and adjust them for current resolution
|
|
xcenter = 640 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn
|
|
ycenter = 480 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn
|
|
|
|
VectorSubtract (worldCoord, cg.refdef.vieworg, local);
|
|
|
|
transformed[0] = DotProduct(local,vright);
|
|
transformed[1] = DotProduct(local,vup);
|
|
transformed[2] = DotProduct(local,vfwd);
|
|
|
|
// Make sure Z is not negative.
|
|
if(transformed[2] < 0.01)
|
|
{
|
|
return qfalse;
|
|
}
|
|
// Simple convert to screen coords.
|
|
float xzi = xcenter / transformed[2] * (90.0/cg.refdef.fov_x);
|
|
float yzi = ycenter / transformed[2] * (90.0/cg.refdef.fov_y);
|
|
|
|
*x = xcenter + xzi * transformed[0];
|
|
*y = ycenter - yzi * transformed[1];
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
qboolean CG_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y )
|
|
{
|
|
float xF, yF;
|
|
qboolean retVal = CG_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF );
|
|
*x = (int)xF;
|
|
*y = (int)yF;
|
|
return retVal;
|
|
}
|
|
*/
|
|
|
|
/*
|
|
================
|
|
CG_DrawZoomMask
|
|
|
|
================
|
|
*/
|
|
static void CG_DrawZoomMask( void )
|
|
{
|
|
vec4_t color1;
|
|
float level;
|
|
static qboolean flip = qtrue;
|
|
|
|
// int ammo = cg_entities[0].gent->client->ps.ammo[weaponData[cent->currentState.weapon].ammoIndex];
|
|
float cx, cy;
|
|
// int val[5];
|
|
float max, fi;
|
|
|
|
// Check for Binocular specific zooming since we'll want to render different bits in each case
|
|
if ( cg.predictedPlayerState.zoomMode == 2 )
|
|
{
|
|
int val, i;
|
|
float off;
|
|
|
|
// zoom level
|
|
level = (float)(80.0f - cg.predictedPlayerState.zoomFov) / 80.0f;
|
|
|
|
// ...so we'll clamp it
|
|
if ( level < 0.0f )
|
|
{
|
|
level = 0.0f;
|
|
}
|
|
else if ( level > 1.0f )
|
|
{
|
|
level = 1.0f;
|
|
}
|
|
|
|
// Using a magic number to convert the zoom level to scale amount
|
|
level *= 162.0f;
|
|
|
|
// draw blue tinted distortion mask, trying to make it as small as is necessary to fill in the viewable area
|
|
trap_R_SetColor( colorTable[CT_WHITE] );
|
|
CG_DrawPic( 34, 48, 570, 362, cgs.media.binocularStatic );
|
|
|
|
// Black out the area behind the numbers
|
|
trap_R_SetColor( colorTable[CT_BLACK]);
|
|
CG_DrawPic( 212, 367, 200, 40, cgs.media.whiteShader );
|
|
|
|
// Numbers should be kind of greenish
|
|
color1[0] = 0.2f;
|
|
color1[1] = 0.4f;
|
|
color1[2] = 0.2f;
|
|
color1[3] = 0.3f;
|
|
trap_R_SetColor( color1 );
|
|
|
|
// Draw scrolling numbers, use intervals 10 units apart--sorry, this section of code is just kind of hacked
|
|
// up with a bunch of magic numbers.....
|
|
val = ((int)((cg.refdef.viewangles[YAW] + 180) / 10)) * 10;
|
|
off = (cg.refdef.viewangles[YAW] + 180) - val;
|
|
|
|
for ( i = -10; i < 30; i += 10 )
|
|
{
|
|
val -= 10;
|
|
|
|
if ( val < 0 )
|
|
{
|
|
val += 360;
|
|
}
|
|
|
|
// we only want to draw the very far left one some of the time, if it's too far to the left it will
|
|
// poke outside the mask.
|
|
if (( off > 3.0f && i == -10 ) || i > -10 )
|
|
{
|
|
// draw the value, but add 200 just to bump the range up...arbitrary, so change it if you like
|
|
CG_DrawNumField( 155 + i * 10 + off * 10, 374, 3, val + 200, 24, 14, NUM_FONT_CHUNKY, qtrue );
|
|
CG_DrawPic( 245 + (i-1) * 10 + off * 10, 376, 6, 6, cgs.media.whiteShader );
|
|
}
|
|
}
|
|
|
|
CG_DrawPic( 212, 367, 200, 28, cgs.media.binocularOverlay );
|
|
|
|
color1[0] = sin( cg.time * 0.01f ) * 0.5f + 0.5f;
|
|
color1[0] = color1[0] * color1[0];
|
|
color1[1] = color1[0];
|
|
color1[2] = color1[0];
|
|
color1[3] = 1.0f;
|
|
|
|
trap_R_SetColor( color1 );
|
|
|
|
CG_DrawPic( 82, 94, 16, 16, cgs.media.binocularCircle );
|
|
|
|
// Flickery color
|
|
color1[0] = 0.7f + crandom() * 0.1f;
|
|
color1[1] = 0.8f + crandom() * 0.1f;
|
|
color1[2] = 0.7f + crandom() * 0.1f;
|
|
color1[3] = 1.0f;
|
|
trap_R_SetColor( color1 );
|
|
|
|
CG_DrawPic( 0, 0, 640, 480, cgs.media.binocularMask );
|
|
|
|
CG_DrawPic( 4, 282 - level, 16, 16, cgs.media.binocularArrow );
|
|
|
|
// The top triangle bit randomly flips
|
|
if ( flip )
|
|
{
|
|
CG_DrawPic( 330, 60, -26, -30, cgs.media.binocularTri );
|
|
}
|
|
else
|
|
{
|
|
CG_DrawPic( 307, 40, 26, 30, cgs.media.binocularTri );
|
|
}
|
|
|
|
if ( random() > 0.98f && ( cg.time & 1024 ))
|
|
{
|
|
flip = !flip;
|
|
}
|
|
}
|
|
else if ( cg.predictedPlayerState.zoomMode)
|
|
{
|
|
// disruptor zoom mode
|
|
level = (float)(50.0f - zoomFov) / 50.0f;//(float)(80.0f - zoomFov) / 80.0f;
|
|
|
|
// ...so we'll clamp it
|
|
if ( level < 0.0f )
|
|
{
|
|
level = 0.0f;
|
|
}
|
|
else if ( level > 1.0f )
|
|
{
|
|
level = 1.0f;
|
|
}
|
|
|
|
// Using a magic number to convert the zoom level to a rotation amount that correlates more or less with the zoom artwork.
|
|
level *= 103.0f;
|
|
|
|
// Draw target mask
|
|
trap_R_SetColor( colorTable[CT_WHITE] );
|
|
CG_DrawPic( 0, 0, 640, 480, cgs.media.disruptorMask );
|
|
|
|
// apparently 99.0f is the full zoom level
|
|
if ( level >= 99 )
|
|
{
|
|
// Fully zoomed, so make the rotating insert pulse
|
|
color1[0] = 1.0f;
|
|
color1[1] = 1.0f;
|
|
color1[2] = 1.0f;
|
|
color1[3] = 0.7f + sin( cg.time * 0.01f ) * 0.3f;
|
|
|
|
trap_R_SetColor( color1 );
|
|
}
|
|
|
|
// Draw rotating insert
|
|
CG_DrawRotatePic2( 320, 240, 640, 480, -level, cgs.media.disruptorInsert );
|
|
|
|
// Increase the light levels under the center of the target
|
|
// CG_DrawPic( 198, 118, 246, 246, cgs.media.disruptorLight );
|
|
|
|
// weirdness.....converting ammo to a base five number scale just to be geeky.
|
|
/* val[0] = ammo % 5;
|
|
val[1] = (ammo / 5) % 5;
|
|
val[2] = (ammo / 25) % 5;
|
|
val[3] = (ammo / 125) % 5;
|
|
val[4] = (ammo / 625) % 5;
|
|
|
|
color1[0] = 0.2f;
|
|
color1[1] = 0.55f + crandom() * 0.1f;
|
|
color1[2] = 0.5f + crandom() * 0.1f;
|
|
color1[3] = 1.0f;
|
|
trap_R_SetColor( color1 );
|
|
|
|
for ( int t = 0; t < 5; t++ )
|
|
{
|
|
cx = 320 + sin( (t*10+45)/57.296f ) * 192;
|
|
cy = 240 + cos( (t*10+45)/57.296f ) * 192;
|
|
|
|
CG_DrawRotatePic2( cx, cy, 24, 38, 45 - t * 10, trap_R_RegisterShader( va("gfx/2d/char%d",val[4-t] )));
|
|
}
|
|
*/
|
|
//max = ( cg_entities[0].gent->health / 100.0f );
|
|
|
|
|
|
if ( (cg.snap->ps.eFlags & EF_DOUBLE_AMMO) )
|
|
{
|
|
max = cg.snap->ps.ammo[weaponData[WP_DISRUPTOR].ammoIndex] / ((float)ammoData[weaponData[WP_DISRUPTOR].ammoIndex].max*2.0f);
|
|
}
|
|
else
|
|
{
|
|
max = cg.snap->ps.ammo[weaponData[WP_DISRUPTOR].ammoIndex] / (float)ammoData[weaponData[WP_DISRUPTOR].ammoIndex].max;
|
|
}
|
|
if ( max > 1.0f )
|
|
{
|
|
max = 1.0f;
|
|
}
|
|
|
|
color1[0] = (1.0f - max) * 2.0f;
|
|
color1[1] = max * 1.5f;
|
|
color1[2] = 0.0f;
|
|
color1[3] = 1.0f;
|
|
|
|
// If we are low on health, make us flash
|
|
if ( max < 0.15f && ( cg.time & 512 ))
|
|
{
|
|
VectorClear( color1 );
|
|
}
|
|
|
|
if ( color1[0] > 1.0f )
|
|
{
|
|
color1[0] = 1.0f;
|
|
}
|
|
|
|
if ( color1[1] > 1.0f )
|
|
{
|
|
color1[1] = 1.0f;
|
|
}
|
|
|
|
trap_R_SetColor( color1 );
|
|
|
|
max *= 58.0f;
|
|
|
|
for (fi = 18.5f; fi <= 18.5f + max; fi+= 3 ) // going from 15 to 45 degrees, with 5 degree increments
|
|
{
|
|
cx = 320 + sin( (fi+90.0f)/57.296f ) * 190;
|
|
cy = 240 + cos( (fi+90.0f)/57.296f ) * 190;
|
|
|
|
CG_DrawRotatePic2( cx, cy, 12, 24, 90 - fi, cgs.media.disruptorInsertTick );
|
|
}
|
|
|
|
if ( cg.predictedPlayerState.weaponstate == WEAPON_CHARGING_ALT )
|
|
{
|
|
trap_R_SetColor( colorTable[CT_WHITE] );
|
|
|
|
// draw the charge level
|
|
max = ( cg.time - cg.predictedPlayerState.weaponChargeTime ) / ( 50.0f * 30.0f ); // bad hardcodedness 50 is disruptor charge unit and 30 is max charge units allowed.
|
|
|
|
if ( max > 1.0f )
|
|
{
|
|
max = 1.0f;
|
|
}
|
|
|
|
trap_R_DrawStretchPic(257, 435, 134*max, 34, 0, 0, max, 1, cgs.media.disruptorChargeShader);
|
|
}
|
|
// trap_R_SetColor( colorTable[CT_WHITE] );
|
|
// CG_DrawPic( 0, 0, 640, 480, cgs.media.disruptorMask );
|
|
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
CG_Draw3DModel
|
|
|
|
================
|
|
*/
|
|
void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, void *ghoul2, int g2radius, qhandle_t skin, vec3_t origin, vec3_t angles ) {
|
|
refdef_t refdef;
|
|
refEntity_t ent;
|
|
|
|
if ( !cg_draw3dIcons.integer || !cg_drawIcons.integer ) {
|
|
return;
|
|
}
|
|
|
|
memset( &refdef, 0, sizeof( refdef ) );
|
|
|
|
memset( &ent, 0, sizeof( ent ) );
|
|
AnglesToAxis( angles, ent.axis );
|
|
VectorCopy( origin, ent.origin );
|
|
ent.hModel = model;
|
|
ent.ghoul2 = ghoul2;
|
|
ent.radius = g2radius;
|
|
ent.customSkin = skin;
|
|
ent.renderfx = RF_NOSHADOW; // no stencil shadows
|
|
|
|
refdef.rdflags = RDF_NOWORLDMODEL;
|
|
|
|
AxisClear( refdef.viewaxis );
|
|
|
|
refdef.fov_x = 30;
|
|
refdef.fov_y = 30;
|
|
|
|
refdef.x = x;
|
|
refdef.y = y;
|
|
refdef.width = w;
|
|
refdef.height = h;
|
|
|
|
refdef.time = cg.time;
|
|
|
|
trap_R_ClearScene();
|
|
trap_R_AddRefEntityToScene( &ent );
|
|
trap_R_RenderScene( &refdef );
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawHead
|
|
|
|
Used for both the status bar and the scoreboard
|
|
================
|
|
*/
|
|
void CG_DrawHead( float x, float y, float w, float h, int clientNum, vec3_t headAngles )
|
|
{
|
|
clientInfo_t *ci;
|
|
|
|
if (clientNum >= MAX_CLIENTS)
|
|
{ //npc?
|
|
return;
|
|
}
|
|
|
|
ci = &cgs.clientinfo[ clientNum ];
|
|
|
|
CG_DrawPic( x, y, w, h, ci->modelIcon );
|
|
|
|
// if they are deferred, draw a cross out
|
|
if ( ci->deferred )
|
|
{
|
|
CG_DrawPic( x, y, w, h, cgs.media.deferShader );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawFlagModel
|
|
|
|
Used for both the status bar and the scoreboard
|
|
================
|
|
*/
|
|
void CG_DrawFlagModel( float x, float y, float w, float h, int team, qboolean force2D ) {
|
|
qhandle_t cm;
|
|
float len;
|
|
vec3_t origin, angles;
|
|
vec3_t mins, maxs;
|
|
qhandle_t handle;
|
|
|
|
if ( !force2D && cg_draw3dIcons.integer ) {
|
|
|
|
VectorClear( angles );
|
|
|
|
cm = cgs.media.redFlagModel;
|
|
|
|
// offset the origin y and z to center the flag
|
|
trap_R_ModelBounds( cm, mins, maxs );
|
|
|
|
origin[2] = -0.5 * ( mins[2] + maxs[2] );
|
|
origin[1] = 0.5 * ( mins[1] + maxs[1] );
|
|
|
|
// calculate distance so the flag nearly fills the box
|
|
// assume heads are taller than wide
|
|
len = 0.5 * ( maxs[2] - mins[2] );
|
|
origin[0] = len / 0.268; // len / tan( fov/2 )
|
|
|
|
angles[YAW] = 60 * sin( cg.time / 2000.0 );;
|
|
|
|
if( team == TEAM_RED ) {
|
|
handle = cgs.media.redFlagModel;
|
|
} else if( team == TEAM_BLUE ) {
|
|
handle = cgs.media.blueFlagModel;
|
|
} else if( team == TEAM_FREE ) {
|
|
handle = 0;//cgs.media.neutralFlagModel;
|
|
} else {
|
|
return;
|
|
}
|
|
CG_Draw3DModel( x, y, w, h, handle, NULL, 0, 0, origin, angles );
|
|
} else if ( cg_drawIcons.integer ) {
|
|
gitem_t *item;
|
|
|
|
if( team == TEAM_RED ) {
|
|
item = BG_FindItemForPowerup( PW_REDFLAG );
|
|
} else if( team == TEAM_BLUE ) {
|
|
item = BG_FindItemForPowerup( PW_BLUEFLAG );
|
|
} else if( team == TEAM_FREE ) {
|
|
item = BG_FindItemForPowerup( PW_NEUTRALFLAG );
|
|
} else {
|
|
return;
|
|
}
|
|
if (item) {
|
|
CG_DrawPic( x, y, w, h, cg_items[ ITEM_INDEX(item) ].icon );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
DrawAmmo
|
|
================
|
|
*/
|
|
void DrawAmmo()
|
|
{
|
|
int x, y;
|
|
|
|
x = SCREEN_WIDTH-80;
|
|
y = SCREEN_HEIGHT-80;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
================
|
|
CG_DrawHealth
|
|
================
|
|
*/
|
|
void CG_DrawHealth( menuDef_t *menuHUD )
|
|
{
|
|
vec4_t calcColor;
|
|
playerState_t *ps;
|
|
int healthAmt;
|
|
int i,currValue,inc;
|
|
itemDef_t *focusItem;
|
|
float percent;
|
|
|
|
// Can we find the menu?
|
|
if (!menuHUD)
|
|
{
|
|
return;
|
|
}
|
|
|
|
ps = &cg.snap->ps;
|
|
|
|
// What's the health?
|
|
healthAmt = ps->stats[STAT_HEALTH];
|
|
if (healthAmt > ps->stats[STAT_MAX_HEALTH])
|
|
{
|
|
healthAmt = ps->stats[STAT_MAX_HEALTH];
|
|
}
|
|
|
|
|
|
inc = (float) ps->stats[STAT_MAX_HEALTH] / MAX_HUD_TICS;
|
|
currValue = healthAmt;
|
|
|
|
// Print the health tics, fading out the one which is partial health
|
|
for (i=(MAX_HUD_TICS-1);i>=0;i--)
|
|
{
|
|
focusItem = Menu_FindItemByName(menuHUD, healthTicName[i]);
|
|
|
|
if (!focusItem) // This is bad
|
|
{
|
|
continue;
|
|
}
|
|
|
|
memcpy(calcColor, hudTintColor, sizeof(vec4_t));
|
|
|
|
if (currValue <= 0) // don't show tic
|
|
{
|
|
break;
|
|
}
|
|
else if (currValue < inc) // partial tic (alpha it out)
|
|
{
|
|
percent = (float) currValue / inc;
|
|
calcColor[3] *= percent; // Fade it out
|
|
}
|
|
|
|
trap_R_SetColor( calcColor);
|
|
|
|
CG_DrawPic(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
focusItem->window.background
|
|
);
|
|
|
|
currValue -= inc;
|
|
}
|
|
|
|
// Print the mueric amount
|
|
focusItem = Menu_FindItemByName(menuHUD, "healthamount");
|
|
if (focusItem)
|
|
{
|
|
// Print health amount
|
|
trap_R_SetColor( focusItem->window.foreColor );
|
|
|
|
CG_DrawNumField (
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
3,
|
|
ps->stats[STAT_HEALTH],
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
NUM_FONT_SMALL,
|
|
qfalse);
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawArmor
|
|
================
|
|
*/
|
|
void CG_DrawArmor( menuDef_t *menuHUD )
|
|
{
|
|
vec4_t calcColor;
|
|
playerState_t *ps;
|
|
int armor, maxArmor;
|
|
itemDef_t *focusItem;
|
|
float percent,quarterArmor;
|
|
int i,currValue,inc;
|
|
|
|
//ps = &cg.snap->ps;
|
|
ps = &cg.predictedPlayerState;
|
|
|
|
// Can we find the menu?
|
|
if (!menuHUD)
|
|
{
|
|
return;
|
|
}
|
|
|
|
armor = ps->stats[STAT_ARMOR];
|
|
maxArmor = ps->stats[STAT_MAX_HEALTH];
|
|
|
|
if (armor> maxArmor)
|
|
{
|
|
armor = maxArmor;
|
|
}
|
|
|
|
currValue = armor;
|
|
inc = (float) maxArmor / MAX_HUD_TICS;
|
|
|
|
memcpy(calcColor, hudTintColor, sizeof(vec4_t));
|
|
for (i=(MAX_HUD_TICS-1);i>=0;i--)
|
|
{
|
|
focusItem = Menu_FindItemByName(menuHUD, armorTicName[i]);
|
|
|
|
if (!focusItem) // This is bad
|
|
{
|
|
continue;
|
|
}
|
|
|
|
memcpy(calcColor, hudTintColor, sizeof(vec4_t));
|
|
|
|
if (currValue <= 0) // don't show tic
|
|
{
|
|
break;
|
|
}
|
|
else if (currValue < inc) // partial tic (alpha it out)
|
|
{
|
|
percent = (float) currValue / inc;
|
|
calcColor[3] *= percent;
|
|
}
|
|
|
|
trap_R_SetColor( calcColor);
|
|
|
|
if ((i==(MAX_HUD_TICS-1)) && (currValue < inc))
|
|
{
|
|
if (cg.HUDArmorFlag)
|
|
{
|
|
CG_DrawPic(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
focusItem->window.background
|
|
);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CG_DrawPic(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
focusItem->window.background
|
|
);
|
|
}
|
|
|
|
currValue -= inc;
|
|
}
|
|
|
|
focusItem = Menu_FindItemByName(menuHUD, "armoramount");
|
|
|
|
if (focusItem)
|
|
{
|
|
// Print armor amount
|
|
trap_R_SetColor( focusItem->window.foreColor );
|
|
|
|
CG_DrawNumField (
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
3,
|
|
armor,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
NUM_FONT_SMALL,
|
|
qfalse);
|
|
}
|
|
|
|
// If armor is low, flash a graphic to warn the player
|
|
if (armor) // Is there armor? Draw the HUD Armor TIC
|
|
{
|
|
quarterArmor = (float) (ps->stats[STAT_MAX_HEALTH] / 4.0f);
|
|
|
|
// Make tic flash if armor is at 25% of full armor
|
|
if (ps->stats[STAT_ARMOR] < quarterArmor) // Do whatever the flash timer says
|
|
{
|
|
if (cg.HUDTickFlashTime < cg.time) // Flip at the same time
|
|
{
|
|
cg.HUDTickFlashTime = cg.time + 400;
|
|
if (cg.HUDArmorFlag)
|
|
{
|
|
cg.HUDArmorFlag = qfalse;
|
|
}
|
|
else
|
|
{
|
|
cg.HUDArmorFlag = qtrue;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cg.HUDArmorFlag=qtrue;
|
|
}
|
|
}
|
|
else // No armor? Don't show it.
|
|
{
|
|
cg.HUDArmorFlag=qfalse;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawSaberStyle
|
|
|
|
If the weapon is a light saber (which needs no ammo) then draw a graphic showing
|
|
the saber style (fast, medium, strong)
|
|
================
|
|
*/
|
|
static void CG_DrawSaberStyle( centity_t *cent, menuDef_t *menuHUD)
|
|
{
|
|
itemDef_t *focusItem;
|
|
|
|
if (!cent->currentState.weapon ) // We don't have a weapon right now
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cent->currentState.weapon != WP_SABER )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Can we find the menu?
|
|
if (!menuHUD)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
// draw the current saber style in this window
|
|
switch ( cg.predictedPlayerState.fd.saberDrawAnimLevel )
|
|
{
|
|
case 1://FORCE_LEVEL_1:
|
|
case 5://FORCE_LEVEL_5://Tavion
|
|
|
|
focusItem = Menu_FindItemByName(menuHUD, "saberstyle_fast");
|
|
|
|
if (focusItem)
|
|
{
|
|
trap_R_SetColor( hudTintColor );
|
|
|
|
CG_DrawPic(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
focusItem->window.background
|
|
);
|
|
}
|
|
|
|
break;
|
|
case 2://FORCE_LEVEL_2:
|
|
case 6://SS_DUAL
|
|
case 7://SS_STAFF
|
|
focusItem = Menu_FindItemByName(menuHUD, "saberstyle_medium");
|
|
|
|
if (focusItem)
|
|
{
|
|
trap_R_SetColor( hudTintColor );
|
|
|
|
CG_DrawPic(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
focusItem->window.background
|
|
);
|
|
}
|
|
break;
|
|
case 3://FORCE_LEVEL_3:
|
|
case 4://FORCE_LEVEL_4://Desann
|
|
focusItem = Menu_FindItemByName(menuHUD, "saberstyle_strong");
|
|
|
|
if (focusItem)
|
|
{
|
|
trap_R_SetColor( hudTintColor );
|
|
|
|
CG_DrawPic(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
focusItem->window.background
|
|
);
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawAmmo
|
|
================
|
|
*/
|
|
static void CG_DrawAmmo( centity_t *cent,menuDef_t *menuHUD)
|
|
{
|
|
playerState_t *ps;
|
|
int i;
|
|
vec4_t calcColor;
|
|
float value,inc = 0.0f,percent;
|
|
itemDef_t *focusItem;
|
|
|
|
ps = &cg.snap->ps;
|
|
|
|
// Can we find the menu?
|
|
if (!menuHUD)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!cent->currentState.weapon ) // We don't have a weapon right now
|
|
{
|
|
return;
|
|
}
|
|
|
|
value = ps->ammo[weaponData[cent->currentState.weapon].ammoIndex];
|
|
if (value < 0) // No ammo
|
|
{
|
|
return;
|
|
}
|
|
|
|
focusItem = Menu_FindItemByName(menuHUD, "ammoamount");
|
|
trap_R_SetColor( hudTintColor );
|
|
|
|
if (weaponData[cent->currentState.weapon].energyPerShot == 0 &&
|
|
weaponData[cent->currentState.weapon].altEnergyPerShot == 0)
|
|
{ //just draw "infinite"
|
|
inc = 8 / MAX_HUD_TICS;
|
|
value = 8;
|
|
|
|
focusItem = Menu_FindItemByName(menuHUD, "ammoinfinite");
|
|
trap_R_SetColor( hudTintColor );
|
|
if (focusItem)
|
|
{
|
|
UI_DrawProportionalString(focusItem->window.rect.x, focusItem->window.rect.y, "--", NUM_FONT_SMALL, focusItem->window.foreColor);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
focusItem = Menu_FindItemByName(menuHUD, "ammoamount");
|
|
trap_R_SetColor( hudTintColor );
|
|
if (focusItem)
|
|
{
|
|
|
|
if ( (cent->currentState.eFlags & EF_DOUBLE_AMMO) )
|
|
{
|
|
inc = (float) (ammoData[weaponData[cent->currentState.weapon].ammoIndex].max*2.0f) / MAX_HUD_TICS;
|
|
}
|
|
else
|
|
{
|
|
inc = (float) ammoData[weaponData[cent->currentState.weapon].ammoIndex].max / MAX_HUD_TICS;
|
|
}
|
|
value =ps->ammo[weaponData[cent->currentState.weapon].ammoIndex];
|
|
|
|
CG_DrawNumField (
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
3,
|
|
value,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
NUM_FONT_SMALL,
|
|
qfalse);
|
|
}
|
|
}
|
|
|
|
// Draw tics
|
|
for (i=MAX_HUD_TICS-1;i>=0;i--)
|
|
{
|
|
focusItem = Menu_FindItemByName(menuHUD, ammoTicName[i]);
|
|
|
|
if (!focusItem)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
memcpy(calcColor, hudTintColor, sizeof(vec4_t));
|
|
|
|
if ( value <= 0 ) // done
|
|
{
|
|
break;
|
|
}
|
|
else if (value < inc) // partial tic
|
|
{
|
|
percent = value / inc;
|
|
calcColor[3] = percent;
|
|
}
|
|
|
|
trap_R_SetColor( calcColor);
|
|
|
|
CG_DrawPic(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
focusItem->window.background
|
|
);
|
|
|
|
value -= inc;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawForcePower
|
|
================
|
|
*/
|
|
void CG_DrawForcePower( menuDef_t *menuHUD )
|
|
{
|
|
int i;
|
|
vec4_t calcColor;
|
|
float value,inc,percent;
|
|
itemDef_t *focusItem;
|
|
const int maxForcePower = 100;
|
|
qboolean flash=qfalse;
|
|
|
|
// Can we find the menu?
|
|
if (!menuHUD)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Make the hud flash by setting forceHUDTotalFlashTime above cg.time
|
|
if (cg.forceHUDTotalFlashTime > cg.time )
|
|
{
|
|
flash = qtrue;
|
|
if (cg.forceHUDNextFlashTime < cg.time)
|
|
{
|
|
cg.forceHUDNextFlashTime = cg.time + 400;
|
|
trap_S_StartSound (NULL, 0, CHAN_LOCAL, cgs.media.noforceSound );
|
|
|
|
if (cg.forceHUDActive)
|
|
{
|
|
cg.forceHUDActive = qfalse;
|
|
}
|
|
else
|
|
{
|
|
cg.forceHUDActive = qtrue;
|
|
}
|
|
|
|
}
|
|
}
|
|
else // turn HUD back on if it had just finished flashing time.
|
|
{
|
|
cg.forceHUDNextFlashTime = 0;
|
|
cg.forceHUDActive = qtrue;
|
|
}
|
|
|
|
// if (!cg.forceHUDActive)
|
|
// {
|
|
// return;
|
|
// }
|
|
|
|
inc = (float) maxForcePower / MAX_HUD_TICS;
|
|
value = cg.snap->ps.fd.forcePower;
|
|
|
|
for (i=MAX_HUD_TICS-1;i>=0;i--)
|
|
{
|
|
focusItem = Menu_FindItemByName(menuHUD, forceTicName[i]);
|
|
|
|
if (!focusItem)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// memcpy(calcColor, hudTintColor, sizeof(vec4_t));
|
|
|
|
if ( value <= 0 ) // done
|
|
{
|
|
break;
|
|
}
|
|
else if (value < inc) // partial tic
|
|
{
|
|
if (flash)
|
|
{
|
|
memcpy(calcColor, colorTable[CT_RED], sizeof(vec4_t));
|
|
}
|
|
else
|
|
{
|
|
memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t));
|
|
}
|
|
|
|
percent = value / inc;
|
|
calcColor[3] = percent;
|
|
}
|
|
else
|
|
{
|
|
if (flash)
|
|
{
|
|
memcpy(calcColor, colorTable[CT_RED], sizeof(vec4_t));
|
|
}
|
|
else
|
|
{
|
|
memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t));
|
|
}
|
|
}
|
|
|
|
trap_R_SetColor( calcColor);
|
|
|
|
CG_DrawPic(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
focusItem->window.background
|
|
);
|
|
|
|
value -= inc;
|
|
}
|
|
|
|
focusItem = Menu_FindItemByName(menuHUD, "forceamount");
|
|
|
|
if (focusItem)
|
|
{
|
|
// Print force amount
|
|
trap_R_SetColor( focusItem->window.foreColor );
|
|
|
|
CG_DrawNumField (
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
3,
|
|
cg.snap->ps.fd.forcePower,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
NUM_FONT_SMALL,
|
|
qfalse);
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawHUD
|
|
================
|
|
*/
|
|
void CG_DrawHUD(centity_t *cent)
|
|
{
|
|
menuDef_t *menuHUD = NULL;
|
|
itemDef_t *focusItem = NULL;
|
|
const char *scoreStr = NULL;
|
|
int scoreBias;
|
|
char scoreBiasStr[16];
|
|
|
|
if (cg_hudFiles.integer)
|
|
{
|
|
int x = 0;
|
|
int y = SCREEN_HEIGHT-80;
|
|
char ammoString[64];
|
|
int weapX = x;
|
|
|
|
UI_DrawProportionalString( x+16, y+40, va( "%i", cg.snap->ps.stats[STAT_HEALTH] ),
|
|
UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_RED] );
|
|
|
|
UI_DrawProportionalString( x+18+14, y+40+14, va( "%i", cg.snap->ps.stats[STAT_ARMOR] ),
|
|
UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_GREEN] );
|
|
|
|
if (cg.snap->ps.weapon == WP_SABER)
|
|
{
|
|
if (cg.snap->ps.fd.saberDrawAnimLevel == SS_DUAL)
|
|
{
|
|
Com_sprintf(ammoString, sizeof(ammoString), "AKIMBO");
|
|
weapX += 16;
|
|
}
|
|
else if (cg.snap->ps.fd.saberDrawAnimLevel == SS_STAFF)
|
|
{
|
|
Com_sprintf(ammoString, sizeof(ammoString), "STAFF");
|
|
weapX += 16;
|
|
}
|
|
else if (cg.snap->ps.fd.saberDrawAnimLevel == FORCE_LEVEL_3)
|
|
{
|
|
Com_sprintf(ammoString, sizeof(ammoString), "STRONG");
|
|
weapX += 16;
|
|
}
|
|
else if (cg.snap->ps.fd.saberDrawAnimLevel == FORCE_LEVEL_2)
|
|
{
|
|
Com_sprintf(ammoString, sizeof(ammoString), "MEDIUM");
|
|
weapX += 16;
|
|
}
|
|
else
|
|
{
|
|
Com_sprintf(ammoString, sizeof(ammoString), "FAST");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Com_sprintf(ammoString, sizeof(ammoString), "%i", cg.snap->ps.ammo[weaponData[cent->currentState.weapon].ammoIndex]);
|
|
}
|
|
|
|
UI_DrawProportionalString( SCREEN_WIDTH-(weapX+16+32), y+40, va( "%s", ammoString ),
|
|
UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_HUD_ORANGE] );
|
|
|
|
UI_DrawProportionalString( SCREEN_WIDTH-(x+18+14+32), y+40+14, va( "%i", cg.snap->ps.fd.forcePower),
|
|
UI_SMALLFONT|UI_DROPSHADOW, colorTable[CT_ICON_BLUE] );
|
|
|
|
return;
|
|
}
|
|
|
|
if (cgs.gametype >= GT_TEAM && cgs.gametype != GT_SIEGE)
|
|
{ // tint the hud items based on team
|
|
if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED )
|
|
hudTintColor = redhudtint;
|
|
else if (cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE )
|
|
hudTintColor = bluehudtint;
|
|
else // If we're not on a team for whatever reason, leave things as they are.
|
|
hudTintColor = colorTable[CT_WHITE];
|
|
}
|
|
else
|
|
{ // tint the hud items white (dont' tint)
|
|
hudTintColor = colorTable[CT_WHITE];
|
|
}
|
|
|
|
// Draw the left HUD
|
|
menuHUD = Menus_FindByName("lefthud");
|
|
if (menuHUD)
|
|
{
|
|
itemDef_t *focusItem;
|
|
|
|
// Print scanline
|
|
focusItem = Menu_FindItemByName(menuHUD, "scanline");
|
|
if (focusItem)
|
|
{
|
|
trap_R_SetColor( hudTintColor );
|
|
CG_DrawPic(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
focusItem->window.background
|
|
);
|
|
}
|
|
|
|
// Print frame
|
|
focusItem = Menu_FindItemByName(menuHUD, "frame");
|
|
if (focusItem)
|
|
{
|
|
trap_R_SetColor( hudTintColor );
|
|
CG_DrawPic(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
focusItem->window.background
|
|
);
|
|
}
|
|
|
|
if (cg.predictedPlayerState.pm_type != PM_SPECTATOR)
|
|
{
|
|
CG_DrawArmor(menuHUD);
|
|
CG_DrawHealth(menuHUD);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//CG_Error("CG_ChatBox_ArrayInsert: unable to locate HUD menu file ");
|
|
}
|
|
|
|
//scoreStr = va("Score: %i", cgs.clientinfo[cg.snap->ps.clientNum].score);
|
|
if ( cgs.gametype == GT_DUEL )
|
|
{//A duel that requires more than one kill to knock the current enemy back to the queue
|
|
//show current kills out of how many needed
|
|
scoreStr = va("%s: %i/%i", CG_GetStringEdString("MP_INGAME", "SCORE"), cg.snap->ps.persistant[PERS_SCORE], cgs.fraglimit);
|
|
}
|
|
else if (0 && cgs.gametype < GT_TEAM )
|
|
{ // This is a teamless mode, draw the score bias.
|
|
scoreBias = cg.snap->ps.persistant[PERS_SCORE] - cgs.scores1;
|
|
if (scoreBias == 0)
|
|
{ // We are the leader!
|
|
if (cgs.scores2 <= 0)
|
|
{ // Nobody to be ahead of yet.
|
|
Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), "");
|
|
}
|
|
else
|
|
{
|
|
scoreBias = cg.snap->ps.persistant[PERS_SCORE] - cgs.scores2;
|
|
if (scoreBias == 0)
|
|
{
|
|
Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), " (Tie)");
|
|
}
|
|
else
|
|
{
|
|
Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), " (+%d)", scoreBias);
|
|
}
|
|
}
|
|
}
|
|
else // if (scoreBias < 0)
|
|
{ // We are behind!
|
|
Com_sprintf(scoreBiasStr, sizeof(scoreBiasStr), " (%d)", scoreBias);
|
|
}
|
|
scoreStr = va("%s: %i%s", CG_GetStringEdString("MP_INGAME", "SCORE"), cg.snap->ps.persistant[PERS_SCORE], scoreBiasStr);
|
|
}
|
|
else
|
|
{ // Don't draw a bias.
|
|
scoreStr = va("%s: %i", CG_GetStringEdString("MP_INGAME", "SCORE"), cg.snap->ps.persistant[PERS_SCORE]);
|
|
}
|
|
|
|
menuHUD = Menus_FindByName("righthud");
|
|
|
|
if (menuHUD)
|
|
{
|
|
if (cgs.gametype != GT_POWERDUEL)
|
|
{
|
|
focusItem = Menu_FindItemByName(menuHUD, "score_line");
|
|
if (focusItem)
|
|
{
|
|
UI_DrawScaledProportionalString(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
scoreStr,
|
|
UI_RIGHT|UI_DROPSHADOW,
|
|
focusItem->window.foreColor,
|
|
0.7);
|
|
}
|
|
}
|
|
|
|
// Print scanline
|
|
focusItem = Menu_FindItemByName(menuHUD, "scanline");
|
|
if (focusItem)
|
|
{
|
|
trap_R_SetColor( hudTintColor );
|
|
CG_DrawPic(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
focusItem->window.background
|
|
);
|
|
}
|
|
|
|
focusItem = Menu_FindItemByName(menuHUD, "frame");
|
|
if (focusItem)
|
|
{
|
|
trap_R_SetColor( hudTintColor );
|
|
CG_DrawPic(
|
|
focusItem->window.rect.x,
|
|
focusItem->window.rect.y,
|
|
focusItem->window.rect.w,
|
|
focusItem->window.rect.h,
|
|
focusItem->window.background
|
|
);
|
|
}
|
|
|
|
CG_DrawForcePower(menuHUD);
|
|
|
|
// Draw ammo tics or saber style
|
|
if ( cent->currentState.weapon == WP_SABER )
|
|
{
|
|
CG_DrawSaberStyle(cent,menuHUD);
|
|
}
|
|
else
|
|
{
|
|
CG_DrawAmmo(cent,menuHUD);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//CG_Error("CG_ChatBox_ArrayInsert: unable to locate HUD menu file ");
|
|
}
|
|
}
|
|
|
|
#define MAX_SHOWPOWERS NUM_FORCE_POWERS
|
|
|
|
qboolean ForcePower_Valid(int i)
|
|
{
|
|
if (i == FP_LEVITATION ||
|
|
i == FP_SABER_OFFENSE ||
|
|
i == FP_SABER_DEFENSE ||
|
|
i == FP_SABERTHROW)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if (cg.snap->ps.fd.forcePowersKnown & (1 << i))
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
CG_DrawForceSelect
|
|
===================
|
|
*/
|
|
#ifdef _XBOX
|
|
extern bool CL_ExtendSelectTime(void);
|
|
#endif
|
|
void CG_DrawForceSelect( void )
|
|
{
|
|
int i;
|
|
int count;
|
|
int smallIconSize,bigIconSize;
|
|
int holdX,x,y,x2,y2,pad,length;
|
|
int sideLeftIconCnt,sideRightIconCnt;
|
|
int sideMax,holdCount,iconCnt;
|
|
int yOffset = 0;
|
|
|
|
|
|
x2 = 0;
|
|
y2 = 0;
|
|
|
|
// don't display if dead
|
|
if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((cg.forceSelectTime+WEAPON_SELECT_TIME)<cg.time) // Time is up for the HUD to display
|
|
{
|
|
cg.forceSelect = cg.snap->ps.fd.forcePowerSelected;
|
|
return;
|
|
}
|
|
|
|
if (!cg.snap->ps.fd.forcePowersKnown)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef _XBOX
|
|
if(CL_ExtendSelectTime()) {
|
|
cg.forceSelectTime = cg.time;
|
|
}
|
|
yOffset = -50;
|
|
#endif
|
|
|
|
// count the number of powers owned
|
|
count = 0;
|
|
|
|
for (i=0;i < NUM_FORCE_POWERS;++i)
|
|
{
|
|
if (ForcePower_Valid(i))
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (count == 0) // If no force powers, don't display
|
|
{
|
|
return;
|
|
}
|
|
|
|
sideMax = 3; // Max number of icons on the side
|
|
|
|
// Calculate how many icons will appear to either side of the center one
|
|
holdCount = count - 1; // -1 for the center icon
|
|
if (holdCount == 0) // No icons to either side
|
|
{
|
|
sideLeftIconCnt = 0;
|
|
sideRightIconCnt = 0;
|
|
}
|
|
else if (count > (2*sideMax)) // Go to the max on each side
|
|
{
|
|
sideLeftIconCnt = sideMax;
|
|
sideRightIconCnt = sideMax;
|
|
}
|
|
else // Less than max, so do the calc
|
|
{
|
|
sideLeftIconCnt = holdCount/2;
|
|
sideRightIconCnt = holdCount - sideLeftIconCnt;
|
|
}
|
|
|
|
smallIconSize = 30;
|
|
bigIconSize = 60;
|
|
pad = 12;
|
|
|
|
x = 320;
|
|
y = 425;
|
|
|
|
// Background
|
|
length = (sideLeftIconCnt * smallIconSize) + (sideLeftIconCnt*pad) +
|
|
bigIconSize + (sideRightIconCnt * smallIconSize) + (sideRightIconCnt*pad) + 12;
|
|
|
|
i = BG_ProperForceIndex(cg.forceSelect) - 1;
|
|
if (i < 0)
|
|
{
|
|
i = MAX_SHOWPOWERS;
|
|
}
|
|
|
|
trap_R_SetColor(NULL);
|
|
// Work backwards from current icon
|
|
holdX = x - ((bigIconSize/2) + pad + smallIconSize);
|
|
for (iconCnt=1;iconCnt<(sideLeftIconCnt+1);i--)
|
|
{
|
|
if (i < 0)
|
|
{
|
|
i = MAX_SHOWPOWERS;
|
|
}
|
|
|
|
if (!ForcePower_Valid(forcePowerSorted[i])) // Does he have this power?
|
|
{
|
|
continue;
|
|
}
|
|
|
|
++iconCnt; // Good icon
|
|
|
|
if (cgs.media.forcePowerIcons[forcePowerSorted[i]])
|
|
{
|
|
CG_DrawPic( holdX, y + yOffset, smallIconSize, smallIconSize, cgs.media.forcePowerIcons[forcePowerSorted[i]] );
|
|
holdX -= (smallIconSize+pad);
|
|
}
|
|
}
|
|
|
|
if (ForcePower_Valid(cg.forceSelect))
|
|
{
|
|
// Current Center Icon
|
|
if (cgs.media.forcePowerIcons[cg.forceSelect])
|
|
{
|
|
CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2)) + yOffset, bigIconSize, bigIconSize, cgs.media.forcePowerIcons[cg.forceSelect] ); //only cache the icon for display
|
|
}
|
|
}
|
|
|
|
i = BG_ProperForceIndex(cg.forceSelect) + 1;
|
|
if (i>MAX_SHOWPOWERS)
|
|
{
|
|
i = 0;
|
|
}
|
|
|
|
// Work forwards from current icon
|
|
holdX = x + (bigIconSize/2) + pad;
|
|
for (iconCnt=1;iconCnt<(sideRightIconCnt+1);i++)
|
|
{
|
|
if (i>MAX_SHOWPOWERS)
|
|
{
|
|
i = 0;
|
|
}
|
|
|
|
if (!ForcePower_Valid(forcePowerSorted[i])) // Does he have this power?
|
|
{
|
|
continue;
|
|
}
|
|
|
|
++iconCnt; // Good icon
|
|
|
|
if (cgs.media.forcePowerIcons[forcePowerSorted[i]])
|
|
{
|
|
CG_DrawPic( holdX, y + yOffset, smallIconSize, smallIconSize, cgs.media.forcePowerIcons[forcePowerSorted[i]] ); //only cache the icon for display
|
|
holdX += (smallIconSize+pad);
|
|
}
|
|
}
|
|
|
|
if ( showPowersName[cg.forceSelect] )
|
|
{
|
|
UI_DrawProportionalString(320, y + 30 + yOffset, CG_GetStringEdString("SP_INGAME", showPowersName[cg.forceSelect]), UI_CENTER | UI_SMALLFONT, colorTable[CT_ICON_BLUE]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
CG_DrawInventorySelect
|
|
===================
|
|
*/
|
|
void CG_DrawInvenSelect( void )
|
|
{
|
|
int i;
|
|
int sideMax,holdCount,iconCnt;
|
|
int smallIconSize,bigIconSize;
|
|
int sideLeftIconCnt,sideRightIconCnt;
|
|
int count;
|
|
int holdX,x,y,y2,pad;
|
|
int height;
|
|
float addX;
|
|
|
|
// don't display if dead
|
|
if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((cg.invenSelectTime+WEAPON_SELECT_TIME)<cg.time) // Time is up for the HUD to display
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!cg.snap->ps.stats[STAT_HOLDABLE_ITEM] || !cg.snap->ps.stats[STAT_HOLDABLE_ITEMS])
|
|
{
|
|
return;
|
|
}
|
|
|
|
#ifdef _XBOX
|
|
if(CL_ExtendSelectTime()) {
|
|
cg.invenSelectTime = cg.time;
|
|
}
|
|
#endif
|
|
|
|
if (cg.itemSelect == -1)
|
|
{
|
|
cg.itemSelect = bg_itemlist[cg.snap->ps.stats[STAT_HOLDABLE_ITEM]].giTag;
|
|
}
|
|
|
|
//const int bits = cg.snap->ps.stats[ STAT_ITEMS ];
|
|
|
|
// count the number of items owned
|
|
count = 0;
|
|
for ( i = 0 ; i < HI_NUM_HOLDABLE ; i++ )
|
|
{
|
|
if (/*CG_InventorySelectable(i) && inv_icons[i]*/
|
|
(cg.snap->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << i)) )
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
if (!count)
|
|
{
|
|
y2 = 0; //err?
|
|
UI_DrawProportionalString(320, y2 + 22, "EMPTY INVENTORY", UI_CENTER | UI_SMALLFONT, colorTable[CT_ICON_BLUE]);
|
|
return;
|
|
}
|
|
|
|
sideMax = 3; // Max number of icons on the side
|
|
|
|
// Calculate how many icons will appear to either side of the center one
|
|
holdCount = count - 1; // -1 for the center icon
|
|
if (holdCount == 0) // No icons to either side
|
|
{
|
|
sideLeftIconCnt = 0;
|
|
sideRightIconCnt = 0;
|
|
}
|
|
else if (count > (2*sideMax)) // Go to the max on each side
|
|
{
|
|
sideLeftIconCnt = sideMax;
|
|
sideRightIconCnt = sideMax;
|
|
}
|
|
else // Less than max, so do the calc
|
|
{
|
|
sideLeftIconCnt = holdCount/2;
|
|
sideRightIconCnt = holdCount - sideLeftIconCnt;
|
|
}
|
|
|
|
i = cg.itemSelect - 1;
|
|
if (i<0)
|
|
{
|
|
i = HI_NUM_HOLDABLE-1;
|
|
}
|
|
|
|
smallIconSize = 40;
|
|
bigIconSize = 80;
|
|
pad = 16;
|
|
|
|
x = 320;
|
|
y = 410;
|
|
|
|
// Left side ICONS
|
|
// Work backwards from current icon
|
|
holdX = x - ((bigIconSize/2) + pad + smallIconSize);
|
|
height = smallIconSize * cg.iconHUDPercent;
|
|
addX = (float) smallIconSize * .75;
|
|
|
|
for (iconCnt=0;iconCnt<sideLeftIconCnt;i--)
|
|
{
|
|
if (i<0)
|
|
{
|
|
i = HI_NUM_HOLDABLE-1;
|
|
}
|
|
|
|
if ( !(cg.snap->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << i)) || i == cg.itemSelect )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
++iconCnt; // Good icon
|
|
|
|
if (!BG_IsItemSelectable(&cg.predictedPlayerState, i))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (cgs.media.invenIcons[i])
|
|
{
|
|
trap_R_SetColor(NULL);
|
|
CG_DrawPic( holdX, y+10, smallIconSize, smallIconSize, cgs.media.invenIcons[i] );
|
|
|
|
trap_R_SetColor(colorTable[CT_ICON_BLUE]);
|
|
/*CG_DrawNumField (holdX + addX, y + smallIconSize, 2, cg.snap->ps.inventory[i], 6, 12,
|
|
NUM_FONT_SMALL,qfalse);
|
|
*/
|
|
|
|
holdX -= (smallIconSize+pad);
|
|
}
|
|
}
|
|
|
|
// Current Center Icon
|
|
height = bigIconSize * cg.iconHUDPercent;
|
|
if (cgs.media.invenIcons[cg.itemSelect] && BG_IsItemSelectable(&cg.predictedPlayerState, cg.itemSelect))
|
|
{
|
|
int itemNdex;
|
|
trap_R_SetColor(NULL);
|
|
CG_DrawPic( x-(bigIconSize/2), (y-((bigIconSize-smallIconSize)/2))+10, bigIconSize, bigIconSize, cgs.media.invenIcons[cg.itemSelect] );
|
|
addX = (float) bigIconSize * .75;
|
|
trap_R_SetColor(colorTable[CT_ICON_BLUE]);
|
|
/*CG_DrawNumField ((x-(bigIconSize/2)) + addX, y, 2, cg.snap->ps.inventory[cg.inventorySelect], 6, 12,
|
|
NUM_FONT_SMALL,qfalse);*/
|
|
|
|
itemNdex = BG_GetItemIndexByTag(cg.itemSelect, IT_HOLDABLE);
|
|
if (bg_itemlist[itemNdex].classname)
|
|
{
|
|
vec4_t textColor = { .312f, .75f, .621f, 1.0f };
|
|
char text[1024];
|
|
char upperKey[1024];
|
|
|
|
strcpy(upperKey, bg_itemlist[itemNdex].classname);
|
|
|
|
if ( trap_SP_GetStringTextString( va("SP_INGAME_%s",Q_strupr(upperKey)), text, sizeof( text )))
|
|
{
|
|
UI_DrawProportionalString(320, y+45, text, UI_CENTER | UI_SMALLFONT, textColor);
|
|
}
|
|
else
|
|
{
|
|
UI_DrawProportionalString(320, y+45, bg_itemlist[itemNdex].classname, UI_CENTER | UI_SMALLFONT, textColor);
|
|
}
|
|
}
|
|
}
|
|
|
|
i = cg.itemSelect + 1;
|
|
if (i> HI_NUM_HOLDABLE-1)
|
|
{
|
|
i = 0;
|
|
}
|
|
|
|
// Right side ICONS
|
|
// Work forwards from current icon
|
|
holdX = x + (bigIconSize/2) + pad;
|
|
height = smallIconSize * cg.iconHUDPercent;
|
|
addX = (float) smallIconSize * .75;
|
|
for (iconCnt=0;iconCnt<sideRightIconCnt;i++)
|
|
{
|
|
if (i> HI_NUM_HOLDABLE-1)
|
|
{
|
|
i = 0;
|
|
}
|
|
|
|
if ( !(cg.snap->ps.stats[STAT_HOLDABLE_ITEMS] & (1 << i)) || i == cg.itemSelect )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
++iconCnt; // Good icon
|
|
|
|
if (!BG_IsItemSelectable(&cg.predictedPlayerState, i))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (cgs.media.invenIcons[i])
|
|
{
|
|
trap_R_SetColor(NULL);
|
|
CG_DrawPic( holdX, y+10, smallIconSize, smallIconSize, cgs.media.invenIcons[i] );
|
|
|
|
trap_R_SetColor(colorTable[CT_ICON_BLUE]);
|
|
/*CG_DrawNumField (holdX + addX, y + smallIconSize, 2, cg.snap->ps.inventory[i], 6, 12,
|
|
NUM_FONT_SMALL,qfalse);*/
|
|
|
|
holdX += (smallIconSize+pad);
|
|
}
|
|
}
|
|
}
|
|
|
|
int cg_targVeh = ENTITYNUM_NONE;
|
|
int cg_targVehLastTime = 0;
|
|
qboolean CG_CheckTargetVehicle( centity_t **pTargetVeh, float *alpha )
|
|
{
|
|
int targetNum = ENTITYNUM_NONE;
|
|
centity_t *targetVeh = NULL;
|
|
|
|
if ( !pTargetVeh || !alpha )
|
|
{//hey, where are my pointers?
|
|
return qfalse;
|
|
}
|
|
|
|
*alpha = 1.0f;
|
|
|
|
//FIXME: need to clear all of these when you die?
|
|
if ( cg.predictedPlayerState.rocketLockIndex < ENTITYNUM_WORLD )
|
|
{
|
|
targetNum = cg.predictedPlayerState.rocketLockIndex;
|
|
}
|
|
else if ( cg.crosshairVehNum < ENTITYNUM_WORLD
|
|
&& cg.time - cg.crosshairVehTime < 3000 )
|
|
{//crosshair was on a vehicle in the last 3 seconds
|
|
targetNum = cg.crosshairVehNum;
|
|
}
|
|
else if ( cg.crosshairClientNum < ENTITYNUM_WORLD )
|
|
{
|
|
targetNum = cg.crosshairClientNum;
|
|
}
|
|
|
|
if ( targetNum < MAX_CLIENTS )
|
|
{//real client
|
|
if ( cg_entities[targetNum].currentState.m_iVehicleNum >= MAX_CLIENTS )
|
|
{//in a vehicle
|
|
targetNum = cg_entities[targetNum].currentState.m_iVehicleNum;
|
|
}
|
|
}
|
|
if ( targetNum < ENTITYNUM_WORLD
|
|
&& targetNum >= MAX_CLIENTS )
|
|
{
|
|
centity_t *targetVeh = &cg_entities[targetNum];
|
|
if ( targetVeh->currentState.NPC_class == CLASS_VEHICLE
|
|
&& targetVeh->m_pVehicle
|
|
&& targetVeh->m_pVehicle->m_pVehicleInfo
|
|
&& targetVeh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER )
|
|
{//it's a vehicle
|
|
cg_targVeh = targetNum;
|
|
cg_targVehLastTime = cg.time;
|
|
*alpha = 1.0f;
|
|
}
|
|
else
|
|
{
|
|
targetVeh = NULL;
|
|
}
|
|
}
|
|
if ( !targetVeh )
|
|
{
|
|
if ( cg_targVehLastTime && cg.time - cg_targVehLastTime < 3000 )
|
|
{
|
|
targetVeh = &cg_entities[cg_targVeh];;
|
|
if ( cg.time-cg_targVehLastTime < 1000 )
|
|
{//stay at full alpha for 1 sec after lose them from crosshair
|
|
*alpha = 1.0f;
|
|
}
|
|
else
|
|
{//fade out over 2 secs
|
|
*alpha = 1.0f-((cg.time-cg_targVehLastTime-1000)/2000.0f);
|
|
}
|
|
}
|
|
}
|
|
if ( targetVeh )
|
|
{
|
|
*pTargetVeh = targetVeh;
|
|
return qtrue;
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
#define MAX_VHUD_SHIELD_TICS 12
|
|
#define MAX_VHUD_SPEED_TICS 5
|
|
#define MAX_VHUD_ARMOR_TICS 5
|
|
#define MAX_VHUD_AMMO_TICS 5
|
|
|
|
float CG_DrawVehicleShields( const menuDef_t *menuHUD, const centity_t *veh )
|
|
{
|
|
int i;
|
|
char itemName[64];
|
|
float inc, currValue,maxShields;
|
|
vec4_t calcColor;
|
|
itemDef_t *item;
|
|
float percShields;
|
|
|
|
item = Menu_FindItemByName((menuDef_t *) menuHUD, "armorbackground");
|
|
|
|
if (item)
|
|
{
|
|
trap_R_SetColor( item->window.foreColor );
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
}
|
|
|
|
maxShields = veh->m_pVehicle->m_pVehicleInfo->shields;
|
|
currValue = cg.predictedVehicleState.stats[STAT_ARMOR];
|
|
percShields = (float)currValue/(float)maxShields;
|
|
// Print all the tics of the shield graphic
|
|
// Look at the amount of health left and show only as much of the graphic as there is health.
|
|
// Use alpha to fade out partial section of health
|
|
inc = (float) maxShields / MAX_VHUD_ARMOR_TICS;
|
|
for (i=1;i<=MAX_VHUD_ARMOR_TICS;i++)
|
|
{
|
|
sprintf( itemName, "armor_tic%d", i );
|
|
|
|
item = Menu_FindItemByName((menuDef_t *) menuHUD, itemName);
|
|
|
|
if (!item)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
memcpy(calcColor, item->window.foreColor, sizeof(vec4_t));
|
|
|
|
if (currValue <= 0) // don't show tic
|
|
{
|
|
break;
|
|
}
|
|
else if (currValue < inc) // partial tic (alpha it out)
|
|
{
|
|
float percent = currValue / inc;
|
|
calcColor[3] *= percent; // Fade it out
|
|
}
|
|
|
|
trap_R_SetColor( calcColor);
|
|
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
|
|
currValue -= inc;
|
|
}
|
|
|
|
return percShields;
|
|
}
|
|
|
|
int cg_vehicleAmmoWarning = 0;
|
|
int cg_vehicleAmmoWarningTime = 0;
|
|
void CG_DrawVehicleAmmo( const menuDef_t *menuHUD, const centity_t *veh )
|
|
{
|
|
int i;
|
|
char itemName[64];
|
|
float inc, currValue,maxAmmo;
|
|
vec4_t calcColor;
|
|
itemDef_t *item;
|
|
|
|
item = Menu_FindItemByName((menuDef_t *) menuHUD, "ammobackground");
|
|
|
|
if (item)
|
|
{
|
|
trap_R_SetColor( item->window.foreColor );
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
}
|
|
|
|
maxAmmo = veh->m_pVehicle->m_pVehicleInfo->weapon[0].ammoMax;
|
|
currValue = cg.predictedVehicleState.ammo[0];
|
|
|
|
inc = (float) maxAmmo / MAX_VHUD_AMMO_TICS;
|
|
for (i=1;i<=MAX_VHUD_AMMO_TICS;i++)
|
|
{
|
|
sprintf( itemName, "ammo_tic%d", i );
|
|
|
|
item = Menu_FindItemByName((menuDef_t *)menuHUD, itemName);
|
|
|
|
if (!item)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( cg_vehicleAmmoWarningTime > cg.time
|
|
&& cg_vehicleAmmoWarning == 0 )
|
|
{
|
|
memcpy(calcColor, g_color_table[ColorIndex(COLOR_RED)], sizeof(vec4_t));
|
|
calcColor[3] = sin(cg.time*0.005)*0.5f+0.5f;
|
|
}
|
|
else
|
|
{
|
|
memcpy(calcColor, item->window.foreColor, sizeof(vec4_t));
|
|
|
|
if (currValue <= 0) // don't show tic
|
|
{
|
|
break;
|
|
}
|
|
else if (currValue < inc) // partial tic (alpha it out)
|
|
{
|
|
float percent = currValue / inc;
|
|
calcColor[3] *= percent; // Fade it out
|
|
}
|
|
}
|
|
|
|
trap_R_SetColor( calcColor);
|
|
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
|
|
currValue -= inc;
|
|
}
|
|
}
|
|
|
|
|
|
void CG_DrawVehicleAmmoUpper( const menuDef_t *menuHUD, const centity_t *veh )
|
|
{
|
|
int i;
|
|
char itemName[64];
|
|
float inc, currValue,maxAmmo;
|
|
vec4_t calcColor;
|
|
itemDef_t *item;
|
|
|
|
item = Menu_FindItemByName((menuDef_t *)menuHUD, "ammoupperbackground");
|
|
|
|
if (item)
|
|
{
|
|
trap_R_SetColor( item->window.foreColor );
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
}
|
|
|
|
maxAmmo = veh->m_pVehicle->m_pVehicleInfo->weapon[0].ammoMax;
|
|
currValue = cg.predictedVehicleState.ammo[0];
|
|
|
|
inc = (float) maxAmmo / MAX_VHUD_AMMO_TICS;
|
|
for (i=1;i<MAX_VHUD_AMMO_TICS;i++)
|
|
{
|
|
sprintf( itemName, "ammoupper_tic%d", i );
|
|
|
|
item = Menu_FindItemByName((menuDef_t *)menuHUD, itemName);
|
|
|
|
if (!item)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( cg_vehicleAmmoWarningTime > cg.time
|
|
&& cg_vehicleAmmoWarning == 0 )
|
|
{
|
|
memcpy(calcColor, g_color_table[ColorIndex(COLOR_RED)], sizeof(vec4_t));
|
|
calcColor[3] = sin(cg.time*0.005)*0.5f+0.5f;
|
|
}
|
|
else
|
|
{
|
|
memcpy(calcColor, item->window.foreColor, sizeof(vec4_t));
|
|
|
|
if (currValue <= 0) // don't show tic
|
|
{
|
|
break;
|
|
}
|
|
else if (currValue < inc) // partial tic (alpha it out)
|
|
{
|
|
float percent = currValue / inc;
|
|
calcColor[3] *= percent; // Fade it out
|
|
}
|
|
}
|
|
|
|
trap_R_SetColor( calcColor);
|
|
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
|
|
currValue -= inc;
|
|
}
|
|
}
|
|
|
|
|
|
void CG_DrawVehicleAmmoLower( const menuDef_t *menuHUD, const centity_t *veh )
|
|
{
|
|
int i;
|
|
char itemName[64];
|
|
float inc, currValue,maxAmmo;
|
|
vec4_t calcColor;
|
|
itemDef_t *item;
|
|
|
|
|
|
item = Menu_FindItemByName((menuDef_t *)menuHUD, "ammolowerbackground");
|
|
|
|
if (item)
|
|
{
|
|
trap_R_SetColor( item->window.foreColor );
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
}
|
|
|
|
maxAmmo = veh->m_pVehicle->m_pVehicleInfo->weapon[1].ammoMax;
|
|
currValue = cg.predictedVehicleState.ammo[1];
|
|
|
|
inc = (float) maxAmmo / MAX_VHUD_AMMO_TICS;
|
|
for (i=1;i<MAX_VHUD_AMMO_TICS;i++)
|
|
{
|
|
sprintf( itemName, "ammolower_tic%d", i );
|
|
|
|
item = Menu_FindItemByName((menuDef_t *)menuHUD, itemName);
|
|
|
|
if (!item)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( cg_vehicleAmmoWarningTime > cg.time
|
|
&& cg_vehicleAmmoWarning == 1 )
|
|
{
|
|
memcpy(calcColor, g_color_table[ColorIndex(COLOR_RED)], sizeof(vec4_t));
|
|
calcColor[3] = sin(cg.time*0.005)*0.5f+0.5f;
|
|
}
|
|
else
|
|
{
|
|
memcpy(calcColor, item->window.foreColor, sizeof(vec4_t));
|
|
|
|
if (currValue <= 0) // don't show tic
|
|
{
|
|
break;
|
|
}
|
|
else if (currValue < inc) // partial tic (alpha it out)
|
|
{
|
|
float percent = currValue / inc;
|
|
calcColor[3] *= percent; // Fade it out
|
|
}
|
|
}
|
|
|
|
trap_R_SetColor( calcColor);
|
|
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
|
|
currValue -= inc;
|
|
}
|
|
}
|
|
|
|
// The HUD.menu file has the graphic print with a negative height, so it will print from the bottom up.
|
|
void CG_DrawVehicleTurboRecharge( const menuDef_t *menuHUD, const centity_t *veh )
|
|
{
|
|
itemDef_t *item;
|
|
int height;
|
|
|
|
item = Menu_FindItemByName( (menuDef_t *) menuHUD, "turborecharge");
|
|
|
|
if (item)
|
|
{
|
|
float percent=0.0f;
|
|
int diff = ( cg.time - veh->m_pVehicle->m_iTurboTime );
|
|
|
|
height = item->window.rect.h;
|
|
|
|
if (diff > veh->m_pVehicle->m_pVehicleInfo->turboRecharge)
|
|
{
|
|
percent = 1.0f;
|
|
trap_R_SetColor( colorTable[CT_GREEN] );
|
|
}
|
|
else
|
|
{
|
|
percent = (float) diff / veh->m_pVehicle->m_pVehicleInfo->turboRecharge;
|
|
if (percent < 0.0f)
|
|
{
|
|
percent = 0.0f;
|
|
}
|
|
trap_R_SetColor( colorTable[CT_RED] );
|
|
}
|
|
|
|
height *= percent;
|
|
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
height,
|
|
cgs.media.whiteShader);
|
|
}
|
|
}
|
|
|
|
qboolean cg_drawLink = qfalse;
|
|
void CG_DrawVehicleWeaponsLinked( const menuDef_t *menuHUD, const centity_t *veh )
|
|
{
|
|
qboolean drawLink = qfalse;
|
|
if ( veh->m_pVehicle
|
|
&& veh->m_pVehicle->m_pVehicleInfo
|
|
&& (veh->m_pVehicle->m_pVehicleInfo->weapon[0].linkable == 2|| veh->m_pVehicle->m_pVehicleInfo->weapon[1].linkable == 2) )
|
|
{//weapon is always linked
|
|
drawLink = qtrue;
|
|
}
|
|
else
|
|
{
|
|
//MP way:
|
|
//must get sent over network
|
|
if ( cg.predictedVehicleState.vehWeaponsLinked )
|
|
{
|
|
drawLink = qtrue;
|
|
}
|
|
//NOTE: below is SP way
|
|
/*
|
|
//just cheat it
|
|
if ( veh->gent->m_pVehicle->weaponStatus[0].linked
|
|
|| veh->gent->m_pVehicle->weaponStatus[1].linked )
|
|
{
|
|
drawLink = qtrue;
|
|
}
|
|
*/
|
|
}
|
|
|
|
if ( cg_drawLink != drawLink )
|
|
{//state changed, play sound
|
|
cg_drawLink = drawLink;
|
|
trap_S_StartSound (NULL, cg.predictedPlayerState.clientNum, CHAN_LOCAL, trap_S_RegisterSound( "sound/vehicles/common/linkweaps.wav" ) );
|
|
}
|
|
|
|
if ( drawLink )
|
|
{
|
|
itemDef_t *item;
|
|
|
|
item = Menu_FindItemByName( (menuDef_t *) menuHUD, "weaponslinked");
|
|
|
|
if (item)
|
|
{
|
|
trap_R_SetColor( colorTable[CT_CYAN] );
|
|
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
cgs.media.whiteShader);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CG_DrawVehicleSpeed( const menuDef_t *menuHUD, const centity_t *veh )
|
|
{
|
|
int i;
|
|
char itemName[64];
|
|
float inc, currValue,maxSpeed;
|
|
vec4_t calcColor;
|
|
itemDef_t *item;
|
|
|
|
item = Menu_FindItemByName((menuDef_t *) menuHUD, "speedbackground");
|
|
|
|
if (item)
|
|
{
|
|
trap_R_SetColor( item->window.foreColor );
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
}
|
|
|
|
maxSpeed = veh->m_pVehicle->m_pVehicleInfo->speedMax;
|
|
currValue = cg.predictedVehicleState.speed;
|
|
|
|
|
|
// Print all the tics of the shield graphic
|
|
// Look at the amount of health left and show only as much of the graphic as there is health.
|
|
// Use alpha to fade out partial section of health
|
|
inc = (float) maxSpeed / MAX_VHUD_SPEED_TICS;
|
|
for (i=1;i<=MAX_VHUD_SPEED_TICS;i++)
|
|
{
|
|
sprintf( itemName, "speed_tic%d", i );
|
|
|
|
item = Menu_FindItemByName((menuDef_t *)menuHUD, itemName);
|
|
|
|
if (!item)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( cg.time > veh->m_pVehicle->m_iTurboTime )
|
|
{
|
|
memcpy(calcColor, item->window.foreColor, sizeof(vec4_t));
|
|
}
|
|
else // In turbo mode
|
|
{
|
|
if (cg.VHUDFlashTime < cg.time)
|
|
{
|
|
cg.VHUDFlashTime = cg.time + 200;
|
|
if (cg.VHUDTurboFlag)
|
|
{
|
|
cg.VHUDTurboFlag = qfalse;
|
|
}
|
|
else
|
|
{
|
|
cg.VHUDTurboFlag = qtrue;
|
|
}
|
|
}
|
|
|
|
if (cg.VHUDTurboFlag)
|
|
{
|
|
memcpy(calcColor, colorTable[CT_LTRED1], sizeof(vec4_t));
|
|
}
|
|
else
|
|
{
|
|
memcpy(calcColor, item->window.foreColor, sizeof(vec4_t));
|
|
}
|
|
}
|
|
|
|
|
|
if (currValue <= 0) // don't show tic
|
|
{
|
|
break;
|
|
}
|
|
else if (currValue < inc) // partial tic (alpha it out)
|
|
{
|
|
float percent = currValue / inc;
|
|
calcColor[3] *= percent; // Fade it out
|
|
}
|
|
|
|
trap_R_SetColor( calcColor);
|
|
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
|
|
currValue -= inc;
|
|
}
|
|
}
|
|
|
|
void CG_DrawVehicleArmor( const menuDef_t *menuHUD, const centity_t *veh )
|
|
{
|
|
int i;
|
|
vec4_t calcColor;
|
|
char itemName[64];
|
|
float inc, currValue,maxArmor;
|
|
itemDef_t *item;
|
|
|
|
maxArmor = veh->m_pVehicle->m_pVehicleInfo->armor;
|
|
currValue = cg.predictedVehicleState.stats[STAT_HEALTH];
|
|
|
|
item = Menu_FindItemByName( (menuDef_t *) menuHUD, "shieldbackground");
|
|
|
|
if (item)
|
|
{
|
|
trap_R_SetColor( item->window.foreColor );
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
}
|
|
|
|
|
|
// Print all the tics of the shield graphic
|
|
// Look at the amount of health left and show only as much of the graphic as there is health.
|
|
// Use alpha to fade out partial section of health
|
|
inc = (float) maxArmor / MAX_VHUD_SHIELD_TICS;
|
|
for (i=1;i <= MAX_VHUD_SHIELD_TICS;i++)
|
|
{
|
|
sprintf( itemName, "shield_tic%d", i );
|
|
|
|
item = Menu_FindItemByName((menuDef_t *) menuHUD, itemName);
|
|
|
|
if (!item)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
memcpy(calcColor, item->window.foreColor, sizeof(vec4_t));
|
|
|
|
if (currValue <= 0) // don't show tic
|
|
{
|
|
break;
|
|
}
|
|
else if (currValue < inc) // partial tic (alpha it out)
|
|
{
|
|
float percent = currValue / inc;
|
|
calcColor[3] *= percent; // Fade it out
|
|
}
|
|
|
|
trap_R_SetColor( calcColor);
|
|
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
|
|
currValue -= inc;
|
|
}
|
|
}
|
|
|
|
enum
|
|
{
|
|
VEH_DAMAGE_FRONT=0,
|
|
VEH_DAMAGE_BACK,
|
|
VEH_DAMAGE_LEFT,
|
|
VEH_DAMAGE_RIGHT,
|
|
};
|
|
|
|
typedef struct
|
|
{
|
|
char *itemName;
|
|
short heavyDamage;
|
|
short lightDamage;
|
|
} veh_damage_t;
|
|
|
|
veh_damage_t vehDamageData[4] =
|
|
{
|
|
"vehicle_front",SHIPSURF_DAMAGE_FRONT_HEAVY,SHIPSURF_DAMAGE_FRONT_LIGHT,
|
|
"vehicle_back",SHIPSURF_DAMAGE_BACK_HEAVY,SHIPSURF_DAMAGE_BACK_LIGHT,
|
|
"vehicle_left",SHIPSURF_DAMAGE_LEFT_HEAVY,SHIPSURF_DAMAGE_LEFT_LIGHT,
|
|
"vehicle_right",SHIPSURF_DAMAGE_RIGHT_HEAVY,SHIPSURF_DAMAGE_RIGHT_LIGHT,
|
|
};
|
|
|
|
// Draw health graphic for given part of vehicle
|
|
void CG_DrawVehicleDamage(const centity_t *veh,int brokenLimbs,const menuDef_t *menuHUD,float alpha,int index)
|
|
{
|
|
itemDef_t *item;
|
|
int colorI;
|
|
vec4_t color;
|
|
int graphicHandle=0;
|
|
|
|
item = Menu_FindItemByName((menuDef_t *)menuHUD, vehDamageData[index].itemName);
|
|
if (item)
|
|
{
|
|
if (brokenLimbs & (1<<vehDamageData[index].heavyDamage))
|
|
{
|
|
colorI = CT_RED;
|
|
if (brokenLimbs & (1<<vehDamageData[index].lightDamage))
|
|
{
|
|
colorI = CT_DKGREY;
|
|
}
|
|
}
|
|
else if (brokenLimbs & (1<<vehDamageData[index].lightDamage))
|
|
{
|
|
colorI = CT_YELLOW;
|
|
}
|
|
else
|
|
{
|
|
colorI = CT_GREEN;
|
|
}
|
|
|
|
VectorCopy4 ( colorTable[colorI], color );
|
|
color[3] = alpha;
|
|
trap_R_SetColor( color );
|
|
|
|
switch ( index )
|
|
{
|
|
case VEH_DAMAGE_FRONT :
|
|
graphicHandle = veh->m_pVehicle->m_pVehicleInfo->iconFrontHandle;
|
|
break;
|
|
case VEH_DAMAGE_BACK :
|
|
graphicHandle = veh->m_pVehicle->m_pVehicleInfo->iconBackHandle;
|
|
break;
|
|
case VEH_DAMAGE_LEFT :
|
|
graphicHandle = veh->m_pVehicle->m_pVehicleInfo->iconLeftHandle;
|
|
break;
|
|
case VEH_DAMAGE_RIGHT :
|
|
graphicHandle = veh->m_pVehicle->m_pVehicleInfo->iconRightHandle;
|
|
break;
|
|
}
|
|
|
|
if (graphicHandle)
|
|
{
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
graphicHandle );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Used on both damage indicators : player vehicle and the vehicle the player is locked on
|
|
void CG_DrawVehicleDamageHUD(const centity_t *veh,int brokenLimbs,float percShields,char *menuName, float alpha)
|
|
{
|
|
menuDef_t *menuHUD;
|
|
itemDef_t *item;
|
|
vec4_t color;
|
|
|
|
menuHUD = Menus_FindByName(menuName);
|
|
|
|
if ( !menuHUD )
|
|
{
|
|
return;
|
|
}
|
|
|
|
item = Menu_FindItemByName(menuHUD, "background");
|
|
if (item)
|
|
{
|
|
if (veh->m_pVehicle->m_pVehicleInfo->dmgIndicBackgroundHandle)
|
|
{
|
|
if ( veh->damageTime > cg.time )
|
|
{//ship shields currently taking damage
|
|
//NOTE: cent->damageAngle can be accessed to get the direction from the ship origin to the impact point (in 3-D space)
|
|
float perc = 1.0f - ((veh->damageTime - cg.time) / 2000.0f/*MIN_SHIELD_TIME*/);
|
|
if ( perc < 0.0f )
|
|
{
|
|
perc = 0.0f;
|
|
}
|
|
else if ( perc > 1.0f )
|
|
{
|
|
perc = 1.0f;
|
|
}
|
|
color[0] = item->window.foreColor[0];//flash red
|
|
color[1] = item->window.foreColor[1]*perc;//fade other colors back in over time
|
|
color[2] = item->window.foreColor[2]*perc;//fade other colors back in over time
|
|
color[3] = item->window.foreColor[3];//always normal alpha
|
|
trap_R_SetColor( color );
|
|
}
|
|
else
|
|
{
|
|
trap_R_SetColor( item->window.foreColor );
|
|
}
|
|
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
veh->m_pVehicle->m_pVehicleInfo->dmgIndicBackgroundHandle );
|
|
}
|
|
}
|
|
|
|
item = Menu_FindItemByName(menuHUD, "outer_frame");
|
|
if (item)
|
|
{
|
|
if (veh->m_pVehicle->m_pVehicleInfo->dmgIndicFrameHandle)
|
|
{
|
|
trap_R_SetColor( item->window.foreColor );
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
veh->m_pVehicle->m_pVehicleInfo->dmgIndicFrameHandle );
|
|
}
|
|
}
|
|
|
|
item = Menu_FindItemByName(menuHUD, "shields");
|
|
if (item)
|
|
{
|
|
if (veh->m_pVehicle->m_pVehicleInfo->dmgIndicShieldHandle)
|
|
{
|
|
VectorCopy4 ( colorTable[CT_HUD_GREEN], color );
|
|
color[3] = percShields;
|
|
trap_R_SetColor( color );
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
veh->m_pVehicle->m_pVehicleInfo->dmgIndicShieldHandle );
|
|
}
|
|
}
|
|
|
|
//TODO: if we check nextState.brokenLimbs & prevState.brokenLimbs, we can tell when a damage flag has been added and flash that part of the ship
|
|
//FIXME: when ship explodes, either stop drawing ship or draw all parts black
|
|
CG_DrawVehicleDamage(veh,brokenLimbs,menuHUD,alpha,VEH_DAMAGE_FRONT);
|
|
CG_DrawVehicleDamage(veh,brokenLimbs,menuHUD,alpha,VEH_DAMAGE_BACK);
|
|
CG_DrawVehicleDamage(veh,brokenLimbs,menuHUD,alpha,VEH_DAMAGE_LEFT);
|
|
CG_DrawVehicleDamage(veh,brokenLimbs,menuHUD,alpha,VEH_DAMAGE_RIGHT);
|
|
}
|
|
|
|
qboolean CG_DrawVehicleHud( const centity_t *cent )
|
|
{
|
|
itemDef_t *item;
|
|
menuDef_t *menuHUD;
|
|
playerState_t *ps;
|
|
centity_t *veh;
|
|
float shieldPerc,alpha;
|
|
|
|
menuHUD = Menus_FindByName("swoopvehiclehud");
|
|
if (!menuHUD)
|
|
{
|
|
return qtrue; // Draw player HUD
|
|
}
|
|
|
|
ps = &cg.predictedPlayerState;
|
|
|
|
if (!ps || !(ps->m_iVehicleNum))
|
|
{
|
|
return qtrue; // Draw player HUD
|
|
}
|
|
veh = &cg_entities[ps->m_iVehicleNum];
|
|
|
|
if ( !veh )
|
|
{
|
|
return qtrue; // Draw player HUD
|
|
}
|
|
|
|
CG_DrawVehicleTurboRecharge( menuHUD, veh );
|
|
CG_DrawVehicleWeaponsLinked( menuHUD, veh );
|
|
|
|
item = Menu_FindItemByName(menuHUD, "leftframe");
|
|
|
|
// Draw frame
|
|
if (item)
|
|
{
|
|
trap_R_SetColor( item->window.foreColor );
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
}
|
|
|
|
item = Menu_FindItemByName(menuHUD, "rightframe");
|
|
|
|
if (item)
|
|
{
|
|
trap_R_SetColor( item->window.foreColor );
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
}
|
|
|
|
|
|
CG_DrawVehicleArmor( menuHUD, veh );
|
|
|
|
// Get animal hud for speed
|
|
// if (veh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL)
|
|
// {
|
|
// menuHUD = Menus_FindByName("tauntaunhud");
|
|
// }
|
|
|
|
|
|
CG_DrawVehicleSpeed( menuHUD, veh );
|
|
|
|
// Revert to swoophud
|
|
// if (veh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL)
|
|
// {
|
|
// menuHUD = Menus_FindByName("swoopvehiclehud");
|
|
// }
|
|
|
|
// if (veh->m_pVehicle->m_pVehicleInfo->type == VH_ANIMAL)
|
|
// {
|
|
shieldPerc = CG_DrawVehicleShields( menuHUD, veh );
|
|
// }
|
|
|
|
if (veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID && !veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID)
|
|
{
|
|
CG_DrawVehicleAmmo( menuHUD, veh );
|
|
}
|
|
else if (veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID && veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID)
|
|
{
|
|
CG_DrawVehicleAmmoUpper( menuHUD, veh );
|
|
CG_DrawVehicleAmmoLower( menuHUD, veh );
|
|
}
|
|
|
|
// If he's hidden, he must be in a vehicle
|
|
if (veh->m_pVehicle->m_pVehicleInfo->hideRider)
|
|
{
|
|
CG_DrawVehicleDamageHUD(veh,cg.predictedVehicleState.brokenLimbs,shieldPerc,"vehicledamagehud",1.0f);
|
|
|
|
// Has he targeted an enemy?
|
|
if (CG_CheckTargetVehicle( &veh, &alpha ))
|
|
{
|
|
CG_DrawVehicleDamageHUD(veh,veh->currentState.brokenLimbs,((float)veh->currentState.activeForcePass/10.0f),"enemyvehicledamagehud",alpha);
|
|
}
|
|
|
|
return qfalse; // Don't draw player HUD
|
|
}
|
|
|
|
return qtrue; // Draw player HUD
|
|
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawStats
|
|
|
|
================
|
|
*/
|
|
static void CG_DrawStats( void )
|
|
{
|
|
centity_t *cent;
|
|
playerState_t *ps;
|
|
qboolean drawHUD = qtrue;
|
|
/* playerState_t *ps;
|
|
vec3_t angles;
|
|
// vec3_t origin;
|
|
|
|
if ( cg_drawStatus.integer == 0 ) {
|
|
return;
|
|
}
|
|
*/
|
|
cent = &cg_entities[cg.snap->ps.clientNum];
|
|
/* ps = &cg.snap->ps;
|
|
|
|
VectorClear( angles );
|
|
|
|
// Do start
|
|
if (!cg.interfaceStartupDone)
|
|
{
|
|
CG_InterfaceStartup();
|
|
}
|
|
|
|
cgi_UI_MenuPaintAll();*/
|
|
|
|
if ( cent )
|
|
{
|
|
ps = &cg.predictedPlayerState;
|
|
|
|
if ( (ps->m_iVehicleNum ) ) // In a vehicle???
|
|
{
|
|
drawHUD = CG_DrawVehicleHud( cent );
|
|
}
|
|
}
|
|
|
|
if (drawHUD)
|
|
{
|
|
CG_DrawHUD(cent);
|
|
}
|
|
|
|
/*CG_DrawArmor(cent);
|
|
CG_DrawHealth(cent);
|
|
CG_DrawAmmo(cent);
|
|
|
|
CG_DrawTalk(cent);*/
|
|
}
|
|
|
|
/*
|
|
===================
|
|
CG_DrawPickupItem
|
|
===================
|
|
*/
|
|
static void CG_DrawPickupItem( void ) {
|
|
int value;
|
|
float *fadeColor;
|
|
|
|
value = cg.itemPickup;
|
|
if ( value && cg_items[ value ].icon != -1 )
|
|
{
|
|
fadeColor = CG_FadeColor( cg.itemPickupTime, 3000 );
|
|
if ( fadeColor )
|
|
{
|
|
CG_RegisterItemVisuals( value );
|
|
trap_R_SetColor( fadeColor );
|
|
CG_DrawPic( 573, 320, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon );
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawTeamBackground
|
|
|
|
================
|
|
*/
|
|
void CG_DrawTeamBackground( int x, int y, int w, int h, float alpha, int team )
|
|
{
|
|
vec4_t hcolor;
|
|
|
|
hcolor[3] = alpha;
|
|
if ( team == TEAM_RED ) {
|
|
hcolor[0] = 1;
|
|
hcolor[1] = .2f;
|
|
hcolor[2] = .2f;
|
|
} else if ( team == TEAM_BLUE ) {
|
|
hcolor[0] = .2f;
|
|
hcolor[1] = .2f;
|
|
hcolor[2] = 1;
|
|
} else {
|
|
return;
|
|
}
|
|
// trap_R_SetColor( hcolor );
|
|
|
|
CG_FillRect ( x, y, w, h, hcolor );
|
|
// CG_DrawPic( x, y, w, h, cgs.media.teamStatusBar );
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
|
|
|
|
/*
|
|
===========================================================================================
|
|
|
|
UPPER RIGHT CORNER
|
|
|
|
===========================================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
CG_DrawMiniScoreboard
|
|
================
|
|
*/
|
|
static float CG_DrawMiniScoreboard ( float y )
|
|
{
|
|
char temp[MAX_QPATH];
|
|
int xOffset = 0;
|
|
|
|
#ifdef _XBOX
|
|
xOffset = -40;
|
|
#endif
|
|
|
|
if ( !cg_drawScores.integer )
|
|
{
|
|
return y;
|
|
}
|
|
|
|
if (cgs.gametype == GT_SIEGE)
|
|
{ //don't bother with this in siege
|
|
return y;
|
|
}
|
|
|
|
if ( cgs.gametype >= GT_TEAM )
|
|
{
|
|
strcpy ( temp, va("%s: ", CG_GetStringEdString("MP_INGAME", "RED")));
|
|
Q_strcat ( temp, MAX_QPATH, cgs.scores1==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores1)) );
|
|
Q_strcat ( temp, MAX_QPATH, va(" %s: ", CG_GetStringEdString("MP_INGAME", "BLUE")) );
|
|
Q_strcat ( temp, MAX_QPATH, cgs.scores2==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores2)) );
|
|
|
|
CG_Text_Paint( 630 - CG_Text_Width ( temp, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, temp, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM );
|
|
y += 15;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
strcpy ( temp, "1st: " );
|
|
Q_strcat ( temp, MAX_QPATH, cgs.scores1==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores1)) );
|
|
|
|
Q_strcat ( temp, MAX_QPATH, " 2nd: " );
|
|
Q_strcat ( temp, MAX_QPATH, cgs.scores2==SCORE_NOT_PRESENT?"-":(va("%i",cgs.scores2)) );
|
|
|
|
CG_Text_Paint( 630 - CG_Text_Width ( temp, 0.7f, FONT_SMALL ), y, 0.7f, colorWhite, temp, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM );
|
|
y += 15;
|
|
*/
|
|
//rww - no longer doing this. Since the attacker now shows who is first, we print the score there.
|
|
}
|
|
|
|
|
|
return y;
|
|
}
|
|
|
|
/*
|
|
================
|
|
CG_DrawEnemyInfo
|
|
================
|
|
*/
|
|
static float CG_DrawEnemyInfo ( float y )
|
|
{
|
|
float size;
|
|
int clientNum;
|
|
const char *title;
|
|
clientInfo_t *ci;
|
|
int xOffset = 0;
|
|
|
|
if (!cg.snap)
|
|
{
|
|
return y;
|
|
}
|
|
|
|
#ifdef _XBOX
|
|
xOffset = -40;
|
|
#endif
|
|
|
|
if ( !cg_drawEnemyInfo.integer )
|
|
{
|
|
return y;
|
|
}
|
|
|
|
if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 )
|
|
{
|
|
return y;
|
|
}
|
|
|
|
if (cgs.gametype == GT_POWERDUEL)
|
|
{ //just get out of here then
|
|
return y;
|
|
}
|
|
|
|
if ( cgs.gametype == GT_JEDIMASTER )
|
|
{
|
|
//title = "Jedi Master";
|
|
title = CG_GetStringEdString("MP_INGAME", "MASTERY7");
|
|
clientNum = cgs.jediMaster;
|
|
|
|
if ( clientNum < 0 )
|
|
{
|
|
//return y;
|
|
// title = "Get Saber!";
|
|
title = CG_GetStringEdString("MP_INGAME", "GET_SABER");
|
|
|
|
|
|
size = ICON_SIZE * 1.25;
|
|
y += 5;
|
|
|
|
CG_DrawPic( 640 - size - 12 + xOffset, y, size, size, cgs.media.weaponIcons[WP_SABER] );
|
|
|
|
y += size;
|
|
|
|
/*
|
|
CG_Text_Paint( 630 - CG_Text_Width ( ci->name, 0.7f, FONT_MEDIUM ), y, 0.7f, colorWhite, ci->name, 0, 0, 0, FONT_MEDIUM );
|
|
y += 15;
|
|
*/
|
|
|
|
CG_Text_Paint( 630 - CG_Text_Width ( title, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, title, 0, 0, 0, FONT_MEDIUM );
|
|
|
|
return y + BIGCHAR_HEIGHT + 2;
|
|
}
|
|
}
|
|
else if ( cg.snap->ps.duelInProgress )
|
|
{
|
|
// title = "Dueling";
|
|
title = CG_GetStringEdString("MP_INGAME", "DUELING");
|
|
clientNum = cg.snap->ps.duelIndex;
|
|
}
|
|
else if ( cgs.gametype == GT_DUEL && cgs.clientinfo[cg.snap->ps.clientNum].team != TEAM_SPECTATOR)
|
|
{
|
|
title = CG_GetStringEdString("MP_INGAME", "DUELING");
|
|
if (cg.snap->ps.clientNum == cgs.duelist1)
|
|
{
|
|
clientNum = cgs.duelist2; //if power duel, should actually draw both duelists 2 and 3 I guess
|
|
}
|
|
else if (cg.snap->ps.clientNum == cgs.duelist2)
|
|
{
|
|
clientNum = cgs.duelist1;
|
|
}
|
|
else if (cg.snap->ps.clientNum == cgs.duelist3)
|
|
{
|
|
clientNum = cgs.duelist1;
|
|
}
|
|
else
|
|
{
|
|
return y;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
title = "Attacker";
|
|
clientNum = cg.predictedPlayerState.persistant[PERS_ATTACKER];
|
|
|
|
if ( clientNum < 0 || clientNum >= MAX_CLIENTS || clientNum == cg.snap->ps.clientNum )
|
|
{
|
|
return y;
|
|
}
|
|
|
|
if ( cg.time - cg.attackerTime > ATTACKER_HEAD_TIME )
|
|
{
|
|
cg.attackerTime = 0;
|
|
return y;
|
|
}
|
|
*/
|
|
//As of current, we don't want to draw the attacker. Instead, draw whoever is in first place.
|
|
if (cgs.duelWinner < 0 || cgs.duelWinner >= MAX_CLIENTS)
|
|
{
|
|
return y;
|
|
}
|
|
|
|
|
|
title = va("%s: %i",CG_GetStringEdString("MP_INGAME", "LEADER"), cgs.scores1);
|
|
|
|
/*
|
|
if (cgs.scores1 == 1)
|
|
{
|
|
title = va("%i kill", cgs.scores1);
|
|
}
|
|
else
|
|
{
|
|
title = va("%i kills", cgs.scores1);
|
|
}
|
|
*/
|
|
clientNum = cgs.duelWinner;
|
|
}
|
|
|
|
if ( clientNum >= MAX_CLIENTS || !(&cgs.clientinfo[ clientNum ]) )
|
|
{
|
|
return y;
|
|
}
|
|
|
|
ci = &cgs.clientinfo[ clientNum ];
|
|
|
|
size = ICON_SIZE * 1.25;
|
|
y += 5;
|
|
|
|
if ( ci->modelIcon )
|
|
{
|
|
CG_DrawPic( 640 - size - 5 + xOffset, y, size, size, ci->modelIcon );
|
|
}
|
|
|
|
y += size;
|
|
|
|
// CG_Text_Paint( 630 - CG_Text_Width ( ci->name, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, ci->name, 0, 0, 0, FONT_MEDIUM );
|
|
CG_Text_Paint( 630 - CG_Text_Width ( ci->name, 1.0f, FONT_SMALL2 ) + xOffset, y, 1.0f, colorWhite, ci->name, 0, 0, 0, FONT_SMALL2 );
|
|
|
|
y += 15;
|
|
// CG_Text_Paint( 630 - CG_Text_Width ( title, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, title, 0, 0, 0, FONT_MEDIUM );
|
|
CG_Text_Paint( 630 - CG_Text_Width ( title, 1.0f, FONT_SMALL2 ) + xOffset, y, 1.0f, colorWhite, title, 0, 0, 0, FONT_SMALL2 );
|
|
|
|
if ( (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) && cgs.clientinfo[cg.snap->ps.clientNum].team != TEAM_SPECTATOR)
|
|
{//also print their score
|
|
char text[1024];
|
|
y += 15;
|
|
Com_sprintf(text, sizeof(text), "%i/%i", cgs.clientinfo[clientNum].score, cgs.fraglimit );
|
|
CG_Text_Paint( 630 - CG_Text_Width ( text, 0.7f, FONT_MEDIUM ) + xOffset, y, 0.7f, colorWhite, text, 0, 0, 0, FONT_MEDIUM );
|
|
}
|
|
|
|
// nmckenzie: DUEL_HEALTH - fixme - need checks and such here. And this is coded to duelist 1 right now, which is wrongly.
|
|
if ( cgs.showDuelHealths >= 2)
|
|
{
|
|
y += 15;
|
|
if ( cgs.duelist1 == clientNum )
|
|
{
|
|
CG_DrawDuelistHealth ( 640 - size - 5 + xOffset, y, 64, 8, 1 );
|
|
}
|
|
else if ( cgs.duelist2 == clientNum )
|
|
{
|
|
CG_DrawDuelistHealth ( 640 - size - 5 + xOffset, y, 64, 8, 2 );
|
|
}
|
|
}
|
|
|
|
return y + BIGCHAR_HEIGHT + 2;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_DrawSnapshot
|
|
==================
|
|
*/
|
|
static float CG_DrawSnapshot( float y ) {
|
|
char *s;
|
|
int w;
|
|
int xOffset = 0;
|
|
|
|
#ifdef _XBOX
|
|
xOffset = -40;
|
|
#endif
|
|
|
|
s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime,
|
|
cg.latestSnapshotNum, cgs.serverCommandSequence );
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
|
|
CG_DrawBigString( 635 - w + xOffset, y + 2, s, 1.0F);
|
|
|
|
return y + BIGCHAR_HEIGHT + 4;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_DrawFPS
|
|
==================
|
|
*/
|
|
#define FPS_FRAMES 16
|
|
static float CG_DrawFPS( float y ) {
|
|
char *s;
|
|
int w;
|
|
static unsigned short previousTimes[FPS_FRAMES];
|
|
static unsigned short index;
|
|
static int previous, lastupdate;
|
|
int t, i, fps, total;
|
|
unsigned short frameTime;
|
|
#ifdef _XBOX
|
|
const int xOffset = -40;
|
|
#else
|
|
const int xOffset = 0;
|
|
#endif
|
|
|
|
|
|
// 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;
|
|
if (t - lastupdate > 50) //don't sample faster than this
|
|
{
|
|
lastupdate = t;
|
|
previousTimes[index % FPS_FRAMES] = frameTime;
|
|
index++;
|
|
}
|
|
// average multiple frames together to smooth changes out a bit
|
|
total = 0;
|
|
for ( i = 0 ; i < FPS_FRAMES ; i++ ) {
|
|
total += previousTimes[i];
|
|
}
|
|
if ( !total ) {
|
|
total = 1;
|
|
}
|
|
fps = 1000 * FPS_FRAMES / total;
|
|
|
|
s = va( "%ifps", fps );
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
|
|
CG_DrawBigString( 635 - w + xOffset, y + 2, s, 1.0F);
|
|
|
|
return y + BIGCHAR_HEIGHT + 4;
|
|
}
|
|
|
|
// nmckenzie: DUEL_HEALTH
|
|
#define MAX_HEALTH_FOR_IFACE 100
|
|
void CG_DrawHealthBarRough (float x, float y, int width, int height, float ratio, const float *color1, const float *color2)
|
|
{
|
|
float midpoint, remainder;
|
|
float color3[4] = {1, 0, 0, .7f};
|
|
|
|
midpoint = width * ratio - 1;
|
|
remainder = width - midpoint;
|
|
color3[0] = color1[0] * 0.5f;
|
|
|
|
assert(!(height%4));//this won't line up otherwise.
|
|
CG_DrawRect(x + 1, y + height/2-1, midpoint, 1, height/4+1, color1); // creme-y filling.
|
|
CG_DrawRect(x + midpoint, y + height/2-1, remainder, 1, height/4+1, color3); // used-up-ness.
|
|
CG_DrawRect(x, y, width, height, 1, color2); // hard crispy shell
|
|
}
|
|
|
|
void CG_DrawDuelistHealth ( float x, float y, float w, float h, int duelist )
|
|
{
|
|
float duelHealthColor[4] = {1, 0, 0, 0.7f};
|
|
float healthSrc = 0.0f;
|
|
float ratio;
|
|
|
|
if ( duelist == 1 )
|
|
{
|
|
healthSrc = cgs.duelist1health;
|
|
}
|
|
else if (duelist == 2 )
|
|
{
|
|
healthSrc = cgs.duelist2health;
|
|
}
|
|
|
|
ratio = healthSrc / MAX_HEALTH_FOR_IFACE;
|
|
if ( ratio > 1.0f )
|
|
{
|
|
ratio = 1.0f;
|
|
}
|
|
if ( ratio < 0.0f )
|
|
{
|
|
ratio = 0.0f;
|
|
}
|
|
duelHealthColor[0] = (ratio * 0.2f) + 0.5f;
|
|
|
|
CG_DrawHealthBarRough (x, y, w, h, ratio, duelHealthColor, colorTable[CT_WHITE]); // new art for this? I'm not crazy about how this looks.
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawRadar
|
|
=====================
|
|
*/
|
|
//#define RADAR_RANGE 2500
|
|
float cg_radarRange = 2500.0f;
|
|
|
|
#define RADAR_RADIUS 60
|
|
#define RADAR_X (580 - RADAR_RADIUS)
|
|
//#define RADAR_Y 10 //dynamic based on passed Y val
|
|
#define RADAR_CHAT_DURATION 6000
|
|
static int radarLockSoundDebounceTime = 0;
|
|
static int impactSoundDebounceTime = 0;
|
|
#define RADAR_MISSILE_RANGE 3000.0f
|
|
#define RADAR_ASTEROID_RANGE 10000.0f
|
|
#define RADAR_MIN_ASTEROID_SURF_WARN_DIST 1200.0f
|
|
|
|
float CG_DrawRadar ( float y )
|
|
{
|
|
vec4_t color;
|
|
vec4_t teamColor;
|
|
float arrow_w;
|
|
float arrow_h;
|
|
clientInfo_t *cl;
|
|
clientInfo_t *local;
|
|
int i;
|
|
float arrowBaseScale;
|
|
float zScale;
|
|
int xOffset = 0;
|
|
|
|
if (!cg.snap)
|
|
{
|
|
return y;
|
|
}
|
|
|
|
#ifdef _XBOX
|
|
xOffset = -40;
|
|
#endif
|
|
|
|
// Make sure the radar should be showing
|
|
if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 )
|
|
{
|
|
return y;
|
|
}
|
|
|
|
if ( (cg.predictedPlayerState.pm_flags & PMF_FOLLOW) || cg.predictedPlayerState.persistant[PERS_TEAM] == TEAM_SPECTATOR )
|
|
{
|
|
return y;
|
|
}
|
|
|
|
local = &cgs.clientinfo[ cg.snap->ps.clientNum ];
|
|
if ( !local->infoValid )
|
|
{
|
|
return y;
|
|
}
|
|
|
|
// Draw the radar background image
|
|
color[0] = color[1] = color[2] = 1.0f;
|
|
color[3] = 0.6f;
|
|
trap_R_SetColor ( color );
|
|
CG_DrawPic( RADAR_X + xOffset, y, RADAR_RADIUS*2, RADAR_RADIUS*2, cgs.media.radarShader );
|
|
|
|
//Always green for your own team.
|
|
VectorCopy ( g_color_table[ColorIndex(COLOR_GREEN)], teamColor );
|
|
teamColor[3] = 1.0f;
|
|
|
|
// Draw all of the radar entities. Draw them backwards so players are drawn last
|
|
for ( i = cg.radarEntityCount -1 ; i >= 0 ; i-- )
|
|
{
|
|
vec3_t dirLook;
|
|
vec3_t dirPlayer;
|
|
float angleLook;
|
|
float anglePlayer;
|
|
float angle;
|
|
float distance, actualDist;
|
|
centity_t* cent;
|
|
|
|
cent = &cg_entities[cg.radarEntities[i]];
|
|
|
|
// Get the distances first
|
|
VectorSubtract ( cg.predictedPlayerState.origin, cent->lerpOrigin, dirPlayer );
|
|
dirPlayer[2] = 0;
|
|
actualDist = distance = VectorNormalize ( dirPlayer );
|
|
|
|
if ( distance > cg_radarRange * 0.8f)
|
|
{
|
|
if ( (cent->currentState.eFlags & EF_RADAROBJECT)//still want to draw the direction
|
|
|| ( cent->currentState.eType==ET_NPC//FIXME: draw last, with players...
|
|
&& cent->currentState.NPC_class == CLASS_VEHICLE
|
|
&& cent->currentState.speed > 0 ) )//always draw vehicles
|
|
{
|
|
distance = cg_radarRange*0.8f;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
distance = distance / cg_radarRange;
|
|
distance *= RADAR_RADIUS;
|
|
|
|
AngleVectors ( cg.predictedPlayerState.viewangles, dirLook, NULL, NULL );
|
|
|
|
dirLook[2] = 0;
|
|
anglePlayer = atan2(dirPlayer[0],dirPlayer[1]);
|
|
VectorNormalize ( dirLook );
|
|
angleLook = atan2(dirLook[0],dirLook[1]);
|
|
angle = angleLook - anglePlayer;
|
|
|
|
switch ( cent->currentState.eType )
|
|
{
|
|
default:
|
|
{
|
|
float x;
|
|
float ly;
|
|
qhandle_t shader;
|
|
vec4_t color;
|
|
|
|
x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance;
|
|
ly = y + (float)RADAR_RADIUS + (float)cos (angle) * distance;
|
|
|
|
arrowBaseScale = 9.0f;
|
|
shader = 0;
|
|
zScale = 1.0f;
|
|
|
|
//we want to scale the thing up/down based on the relative Z (up/down) positioning
|
|
if (cent->lerpOrigin[2] > cg.predictedPlayerState.origin[2])
|
|
{ //higher, scale up (between 16 and 24)
|
|
float dif = (cent->lerpOrigin[2] - cg.predictedPlayerState.origin[2]);
|
|
|
|
//max out to 1.5x scale at 512 units above local player's height
|
|
dif /= 1024.0f;
|
|
if (dif > 0.5f)
|
|
{
|
|
dif = 0.5f;
|
|
}
|
|
zScale += dif;
|
|
}
|
|
else if (cent->lerpOrigin[2] < cg.predictedPlayerState.origin[2])
|
|
{ //lower, scale down (between 16 and 8)
|
|
float dif = (cg.predictedPlayerState.origin[2] - cent->lerpOrigin[2]);
|
|
|
|
//half scale at 512 units below local player's height
|
|
dif /= 1024.0f;
|
|
if (dif > 0.5f)
|
|
{
|
|
dif = 0.5f;
|
|
}
|
|
zScale -= dif;
|
|
}
|
|
|
|
arrowBaseScale *= zScale;
|
|
|
|
if (cent->currentState.brokenLimbs)
|
|
{ //slightly misleading to use this value, but don't want to add more to entstate.
|
|
//any ent with brokenLimbs non-0 and on radar is an objective ent.
|
|
//brokenLimbs is literal team value.
|
|
char objState[1024];
|
|
int complete;
|
|
|
|
//we only want to draw it if the objective for it is not complete.
|
|
//frame represents objective num.
|
|
trap_Cvar_VariableStringBuffer(va("team%i_objective%i", cent->currentState.brokenLimbs, cent->currentState.frame), objState, 1024);
|
|
|
|
complete = atoi(objState);
|
|
|
|
if (!complete)
|
|
{
|
|
|
|
// generic enemy index specifies a shader to use for the radar entity.
|
|
if ( cent->currentState.genericenemyindex )
|
|
{
|
|
color[0] = color[1] = color[2] = color[3] = 1.0f;
|
|
shader = cgs.gameIcons[cent->currentState.genericenemyindex];
|
|
}
|
|
else
|
|
{
|
|
if (cg.snap &&
|
|
cent->currentState.brokenLimbs == cg.snap->ps.persistant[PERS_TEAM])
|
|
{
|
|
VectorCopy ( g_color_table[ColorIndex(COLOR_RED)], color );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy ( g_color_table[ColorIndex(COLOR_GREEN)], color );
|
|
}
|
|
|
|
shader = cgs.media.siegeItemShader;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
color[0] = color[1] = color[2] = color[3] = 1.0f;
|
|
|
|
// generic enemy index specifies a shader to use for the radar entity.
|
|
if ( cent->currentState.genericenemyindex )
|
|
{
|
|
shader = cgs.gameIcons[cent->currentState.genericenemyindex];
|
|
}
|
|
else
|
|
{
|
|
shader = cgs.media.siegeItemShader;
|
|
}
|
|
|
|
}
|
|
|
|
if ( shader )
|
|
{
|
|
// Pulse the alpha if time2 is set. time2 gets set when the entity takes pain
|
|
if ( (cent->currentState.time2 && cg.time - cent->currentState.time2 < 5000) ||
|
|
(cent->currentState.time2 == 0xFFFFFFFF) )
|
|
{
|
|
if ( (cg.time / 200) & 1 )
|
|
{
|
|
color[3] = 0.1f + 0.9f * (float) (cg.time % 200) / 200.0f;
|
|
}
|
|
else
|
|
{
|
|
color[3] = 1.0f - 0.9f * (float) (cg.time % 200) / 200.0f;
|
|
}
|
|
}
|
|
|
|
trap_R_SetColor ( color );
|
|
CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, shader );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ET_NPC://FIXME: draw last, with players...
|
|
if ( cent->currentState.NPC_class == CLASS_VEHICLE
|
|
&& cent->currentState.speed > 0 )
|
|
{
|
|
if ( cent->m_pVehicle && cent->m_pVehicle->m_pVehicleInfo->radarIconHandle )
|
|
{
|
|
float x;
|
|
float ly;
|
|
|
|
x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance;
|
|
ly = y + (float)RADAR_RADIUS + (float)cos (angle) * distance;
|
|
|
|
arrowBaseScale = 9.0f;
|
|
zScale = 1.0f;
|
|
|
|
//we want to scale the thing up/down based on the relative Z (up/down) positioning
|
|
if (cent->lerpOrigin[2] > cg.predictedPlayerState.origin[2])
|
|
{ //higher, scale up (between 16 and 24)
|
|
float dif = (cent->lerpOrigin[2] - cg.predictedPlayerState.origin[2]);
|
|
|
|
//max out to 1.5x scale at 512 units above local player's height
|
|
dif /= 4096.0f;
|
|
if (dif > 0.5f)
|
|
{
|
|
dif = 0.5f;
|
|
}
|
|
zScale += dif;
|
|
}
|
|
else if (cent->lerpOrigin[2] < cg.predictedPlayerState.origin[2])
|
|
{ //lower, scale down (between 16 and 8)
|
|
float dif = (cg.predictedPlayerState.origin[2] - cent->lerpOrigin[2]);
|
|
|
|
//half scale at 512 units below local player's height
|
|
dif /= 4096.0f;
|
|
if (dif > 0.5f)
|
|
{
|
|
dif = 0.5f;
|
|
}
|
|
zScale -= dif;
|
|
}
|
|
|
|
arrowBaseScale *= zScale;
|
|
|
|
if ( cent->currentState.m_iVehicleNum //vehicle has a driver
|
|
&& cgs.clientinfo[ cent->currentState.m_iVehicleNum-1 ].infoValid )
|
|
{
|
|
if ( cgs.clientinfo[ cent->currentState.m_iVehicleNum-1 ].team == local->team )
|
|
{
|
|
trap_R_SetColor ( teamColor );
|
|
}
|
|
else
|
|
{
|
|
trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
trap_R_SetColor ( NULL );
|
|
}
|
|
CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, cent->m_pVehicle->m_pVehicleInfo->radarIconHandle );
|
|
}
|
|
}
|
|
break; //maybe do something?
|
|
|
|
case ET_MOVER:
|
|
if ( cent->currentState.speed//the mover's size, actually
|
|
&& actualDist < (cent->currentState.speed+RADAR_ASTEROID_RANGE)
|
|
&& cg.predictedPlayerState.m_iVehicleNum )
|
|
{//a mover that's close to me and I'm in a vehicle
|
|
qboolean mayImpact = qfalse;
|
|
float surfaceDist = (actualDist-cent->currentState.speed);
|
|
if ( surfaceDist < 0.0f )
|
|
{
|
|
surfaceDist = 0.0f;
|
|
}
|
|
if ( surfaceDist < RADAR_MIN_ASTEROID_SURF_WARN_DIST )
|
|
{//always warn!
|
|
mayImpact = qtrue;
|
|
}
|
|
else
|
|
{//not close enough to always warn, yet, so check its direction
|
|
vec3_t asteroidPos, myPos, moveDir;
|
|
int predictTime, timeStep = 500;
|
|
float newDist;
|
|
for ( predictTime = timeStep; predictTime < 5000; predictTime+=timeStep )
|
|
{
|
|
//asteroid dir, speed, size, + my dir & speed...
|
|
BG_EvaluateTrajectory( ¢->currentState.pos, cg.time+predictTime, asteroidPos );
|
|
//FIXME: I don't think it's calcing "myPos" correctly
|
|
AngleVectors( cg.predictedVehicleState.viewangles, moveDir, NULL, NULL );
|
|
VectorMA( cg.predictedVehicleState.origin, cg.predictedVehicleState.speed*predictTime/1000.0f, moveDir, myPos );
|
|
newDist = Distance( myPos, asteroidPos );
|
|
if ( (newDist-cent->currentState.speed) <= RADAR_MIN_ASTEROID_SURF_WARN_DIST )//200.0f )
|
|
{//heading for an impact within the next 5 seconds
|
|
mayImpact = qtrue;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if ( mayImpact )
|
|
{//possible collision
|
|
vec4_t asteroidColor = {0.5f,0.5f,0.5f,1.0f};
|
|
float x;
|
|
float ly;
|
|
float asteroidScale = (cent->currentState.speed/2000.0f);//average asteroid radius?
|
|
if ( actualDist > RADAR_ASTEROID_RANGE )
|
|
{
|
|
actualDist = RADAR_ASTEROID_RANGE;
|
|
}
|
|
distance = (actualDist/RADAR_ASTEROID_RANGE)*RADAR_RADIUS;
|
|
|
|
x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance;
|
|
ly = y + (float)RADAR_RADIUS + (float)cos (angle) * distance;
|
|
|
|
if ( asteroidScale > 3.0f )
|
|
{
|
|
asteroidScale = 3.0f;
|
|
}
|
|
else if ( asteroidScale < 0.2f )
|
|
{
|
|
asteroidScale = 0.2f;
|
|
}
|
|
arrowBaseScale = (9.0f*asteroidScale);
|
|
if ( impactSoundDebounceTime < cg.time )
|
|
{
|
|
vec3_t soundOrg;
|
|
if ( surfaceDist > RADAR_ASTEROID_RANGE*0.66f )
|
|
{
|
|
impactSoundDebounceTime = cg.time + 1000;
|
|
}
|
|
else if ( surfaceDist > RADAR_ASTEROID_RANGE/3.0f )
|
|
{
|
|
impactSoundDebounceTime = cg.time + 400;
|
|
}
|
|
else
|
|
{
|
|
impactSoundDebounceTime = cg.time + 100;
|
|
}
|
|
VectorMA( cg.refdef.vieworg, -500.0f*(surfaceDist/RADAR_ASTEROID_RANGE), dirPlayer, soundOrg );
|
|
trap_S_StartSound( soundOrg, ENTITYNUM_WORLD, CHAN_AUTO, trap_S_RegisterSound( "sound/vehicles/common/impactalarm.wav" ) );
|
|
}
|
|
//brighten it the closer it is
|
|
if ( surfaceDist > RADAR_ASTEROID_RANGE*0.66f )
|
|
{
|
|
asteroidColor[0] = asteroidColor[1] = asteroidColor[2] = 0.7f;
|
|
}
|
|
else if ( surfaceDist > RADAR_ASTEROID_RANGE/3.0f )
|
|
{
|
|
asteroidColor[0] = asteroidColor[1] = asteroidColor[2] = 0.85f;
|
|
}
|
|
else
|
|
{
|
|
asteroidColor[0] = asteroidColor[1] = asteroidColor[2] = 1.0f;
|
|
}
|
|
//alpha out the longer it's been since it was considered dangerous
|
|
if ( (cg.time-impactSoundDebounceTime) > 100 )
|
|
{
|
|
asteroidColor[3] = (float)((cg.time-impactSoundDebounceTime)-100)/900.0f;
|
|
}
|
|
|
|
trap_R_SetColor ( asteroidColor );
|
|
CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, trap_R_RegisterShaderNoMip( "gfx/menus/radar/asteroid" ) );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ET_MISSILE:
|
|
if ( //cent->currentState.weapon == WP_ROCKET_LAUNCHER &&//a rocket
|
|
cent->currentState.owner > MAX_CLIENTS //belongs to an NPC
|
|
&& cg_entities[cent->currentState.owner].currentState.NPC_class == CLASS_VEHICLE )
|
|
{//a rocket belonging to an NPC, FIXME: only tracking rockets!
|
|
float x;
|
|
float ly;
|
|
|
|
x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance;
|
|
ly = y + (float)RADAR_RADIUS + (float)cos (angle) * distance;
|
|
|
|
arrowBaseScale = 3.0f;
|
|
if ( cg.predictedPlayerState.m_iVehicleNum )
|
|
{//I'm in a vehicle
|
|
//if it's targetting me, then play an alarm sound if I'm in a vehicle
|
|
if ( cent->currentState.otherEntityNum == cg.predictedPlayerState.clientNum || cent->currentState.otherEntityNum == cg.predictedPlayerState.m_iVehicleNum )
|
|
{
|
|
if ( radarLockSoundDebounceTime < cg.time )
|
|
{
|
|
vec3_t soundOrg;
|
|
int alarmSound;
|
|
if ( actualDist > RADAR_MISSILE_RANGE * 0.66f )
|
|
{
|
|
radarLockSoundDebounceTime = cg.time + 1000;
|
|
arrowBaseScale = 3.0f;
|
|
alarmSound = trap_S_RegisterSound( "sound/vehicles/common/lockalarm1.wav" );
|
|
}
|
|
else if ( actualDist > RADAR_MISSILE_RANGE/3.0f )
|
|
{
|
|
radarLockSoundDebounceTime = cg.time + 500;
|
|
arrowBaseScale = 6.0f;
|
|
alarmSound = trap_S_RegisterSound( "sound/vehicles/common/lockalarm2.wav" );
|
|
}
|
|
else
|
|
{
|
|
radarLockSoundDebounceTime = cg.time + 250;
|
|
arrowBaseScale = 9.0f;
|
|
alarmSound = trap_S_RegisterSound( "sound/vehicles/common/lockalarm3.wav" );
|
|
}
|
|
if ( actualDist > RADAR_MISSILE_RANGE )
|
|
{
|
|
actualDist = RADAR_MISSILE_RANGE;
|
|
}
|
|
VectorMA( cg.refdef.vieworg, -500.0f*(actualDist/RADAR_MISSILE_RANGE), dirPlayer, soundOrg );
|
|
trap_S_StartSound( soundOrg, ENTITYNUM_WORLD, CHAN_AUTO, alarmSound );
|
|
}
|
|
}
|
|
}
|
|
zScale = 1.0f;
|
|
|
|
//we want to scale the thing up/down based on the relative Z (up/down) positioning
|
|
if (cent->lerpOrigin[2] > cg.predictedPlayerState.origin[2])
|
|
{ //higher, scale up (between 16 and 24)
|
|
float dif = (cent->lerpOrigin[2] - cg.predictedPlayerState.origin[2]);
|
|
|
|
//max out to 1.5x scale at 512 units above local player's height
|
|
dif /= 1024.0f;
|
|
if (dif > 0.5f)
|
|
{
|
|
dif = 0.5f;
|
|
}
|
|
zScale += dif;
|
|
}
|
|
else if (cent->lerpOrigin[2] < cg.predictedPlayerState.origin[2])
|
|
{ //lower, scale down (between 16 and 8)
|
|
float dif = (cg.predictedPlayerState.origin[2] - cent->lerpOrigin[2]);
|
|
|
|
//half scale at 512 units below local player's height
|
|
dif /= 1024.0f;
|
|
if (dif > 0.5f)
|
|
{
|
|
dif = 0.5f;
|
|
}
|
|
zScale -= dif;
|
|
}
|
|
|
|
arrowBaseScale *= zScale;
|
|
|
|
if ( cent->currentState.owner >= MAX_CLIENTS//missile owned by an NPC
|
|
&& cg_entities[cent->currentState.owner].currentState.NPC_class == CLASS_VEHICLE//NPC is a vehicle
|
|
&& cg_entities[cent->currentState.owner].currentState.m_iVehicleNum <= MAX_CLIENTS//Vehicle has a player driver
|
|
&& cgs.clientinfo[cg_entities[cent->currentState.owner].currentState.m_iVehicleNum-1].infoValid ) //player driver is valid
|
|
{
|
|
cl = &cgs.clientinfo[cg_entities[cent->currentState.owner].currentState.m_iVehicleNum-1];
|
|
if ( cl->team == local->team )
|
|
{
|
|
trap_R_SetColor ( teamColor );
|
|
}
|
|
else
|
|
{
|
|
trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
trap_R_SetColor ( NULL );
|
|
}
|
|
CG_DrawPic ( x - 4 + xOffset, ly - 4, arrowBaseScale, arrowBaseScale, cgs.media.mAutomapRocketIcon );
|
|
}
|
|
break;
|
|
|
|
case ET_PLAYER:
|
|
{
|
|
vec4_t color;
|
|
|
|
cl = &cgs.clientinfo[ cent->currentState.number ];
|
|
|
|
// not valid then dont draw it
|
|
if ( !cl->infoValid )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
VectorCopy4 ( teamColor, color );
|
|
|
|
arrowBaseScale = 16.0f;
|
|
zScale = 1.0f;
|
|
|
|
// Pulse the radar icon after a voice message
|
|
if ( cent->vChatTime + 2000 > cg.time )
|
|
{
|
|
float f = (cent->vChatTime + 2000 - cg.time) / 3000.0f;
|
|
arrowBaseScale = 16.0f + 4.0f * f;
|
|
color[0] = teamColor[0] + (1.0f - teamColor[0]) * f;
|
|
color[1] = teamColor[1] + (1.0f - teamColor[1]) * f;
|
|
color[2] = teamColor[2] + (1.0f - teamColor[2]) * f;
|
|
}
|
|
|
|
trap_R_SetColor ( color );
|
|
|
|
//we want to scale the thing up/down based on the relative Z (up/down) positioning
|
|
if (cent->lerpOrigin[2] > cg.predictedPlayerState.origin[2])
|
|
{ //higher, scale up (between 16 and 32)
|
|
float dif = (cent->lerpOrigin[2] - cg.predictedPlayerState.origin[2]);
|
|
|
|
//max out to 2x scale at 1024 units above local player's height
|
|
dif /= 1024.0f;
|
|
if (dif > 1.0f)
|
|
{
|
|
dif = 1.0f;
|
|
}
|
|
zScale += dif;
|
|
}
|
|
else if (cent->lerpOrigin[2] < cg.predictedPlayerState.origin[2])
|
|
{ //lower, scale down (between 16 and 8)
|
|
float dif = (cg.predictedPlayerState.origin[2] - cent->lerpOrigin[2]);
|
|
|
|
//half scale at 512 units below local player's height
|
|
dif /= 1024.0f;
|
|
if (dif > 0.5f)
|
|
{
|
|
dif = 0.5f;
|
|
}
|
|
zScale -= dif;
|
|
}
|
|
|
|
arrowBaseScale *= zScale;
|
|
|
|
arrow_w = arrowBaseScale * RADAR_RADIUS / 128;
|
|
arrow_h = arrowBaseScale * RADAR_RADIUS / 128;
|
|
|
|
CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS + sin (angle) * distance + xOffset,
|
|
y + RADAR_RADIUS + cos (angle) * distance,
|
|
arrow_w, arrow_h,
|
|
(360 - cent->lerpAngles[YAW]) + cg.predictedPlayerState.viewangles[YAW], cgs.media.mAutomapPlayerIcon );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
arrowBaseScale = 16.0f;
|
|
|
|
arrow_w = arrowBaseScale * RADAR_RADIUS / 128;
|
|
arrow_h = arrowBaseScale * RADAR_RADIUS / 128;
|
|
|
|
trap_R_SetColor ( colorWhite );
|
|
CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS + xOffset, y + RADAR_RADIUS, arrow_w, arrow_h,
|
|
0, cgs.media.mAutomapPlayerIcon );
|
|
|
|
return y+(RADAR_RADIUS*2);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawTimer
|
|
=================
|
|
*/
|
|
static float CG_DrawTimer( float y ) {
|
|
char *s;
|
|
int w;
|
|
int mins, seconds, tens;
|
|
int msec;
|
|
int xOffset = 0;
|
|
|
|
#ifdef _XBOX
|
|
xOffset = -40;
|
|
#endif
|
|
|
|
msec = cg.time - cgs.levelStartTime;
|
|
|
|
seconds = msec / 1000;
|
|
mins = seconds / 60;
|
|
seconds -= mins * 60;
|
|
tens = seconds / 10;
|
|
seconds -= tens * 10;
|
|
|
|
s = va( "%i:%i%i", mins, tens, seconds );
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
|
|
CG_DrawBigString( 635 - w + xOffset, y + 2, s, 1.0F);
|
|
|
|
return y + BIGCHAR_HEIGHT + 4;
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
CG_DrawTeamOverlay
|
|
=================
|
|
*/
|
|
extern const char *CG_GetLocationString(const char *loc); //cg_main.c
|
|
static float CG_DrawTeamOverlay( float y, qboolean right, qboolean upper ) {
|
|
int x, w, h, xx;
|
|
int i, j, len;
|
|
const char *p;
|
|
vec4_t hcolor;
|
|
int pwidth, lwidth;
|
|
int plyrs;
|
|
char st[16];
|
|
clientInfo_t *ci;
|
|
gitem_t *item;
|
|
int ret_y, count;
|
|
int xOffset = 0;
|
|
|
|
#ifdef _XBOX
|
|
xOffset = -40;
|
|
#endif
|
|
|
|
if ( !cg_drawTeamOverlay.integer ) {
|
|
return y;
|
|
}
|
|
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] != TEAM_RED && cg.snap->ps.persistant[PERS_TEAM] != TEAM_BLUE ) {
|
|
return y; // Not on any team
|
|
}
|
|
|
|
plyrs = 0;
|
|
|
|
// max player name width
|
|
pwidth = 0;
|
|
count = (numSortedTeamPlayers > 8) ? 8 : numSortedTeamPlayers;
|
|
for (i = 0; i < count; i++) {
|
|
ci = cgs.clientinfo + sortedTeamPlayers[i];
|
|
if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) {
|
|
plyrs++;
|
|
len = CG_DrawStrlen(ci->name);
|
|
if (len > pwidth)
|
|
pwidth = len;
|
|
}
|
|
}
|
|
|
|
if (!plyrs)
|
|
return y;
|
|
|
|
if (pwidth > TEAM_OVERLAY_MAXNAME_WIDTH)
|
|
pwidth = TEAM_OVERLAY_MAXNAME_WIDTH;
|
|
|
|
// max location name width
|
|
lwidth = 0;
|
|
for (i = 1; i < MAX_LOCATIONS; i++) {
|
|
p = CG_GetLocationString(CG_ConfigString(CS_LOCATIONS+i));
|
|
if (p && *p) {
|
|
len = CG_DrawStrlen(p);
|
|
if (len > lwidth)
|
|
lwidth = len;
|
|
}
|
|
}
|
|
|
|
if (lwidth > TEAM_OVERLAY_MAXLOCATION_WIDTH)
|
|
lwidth = TEAM_OVERLAY_MAXLOCATION_WIDTH;
|
|
|
|
w = (pwidth + lwidth + 4 + 7) * TINYCHAR_WIDTH;
|
|
|
|
if ( right )
|
|
x = 640 - w;
|
|
else
|
|
x = 0;
|
|
|
|
h = plyrs * TINYCHAR_HEIGHT;
|
|
|
|
if ( upper ) {
|
|
ret_y = y + h;
|
|
} else {
|
|
y -= h;
|
|
ret_y = y;
|
|
}
|
|
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_RED ) {
|
|
hcolor[0] = 1.0f;
|
|
hcolor[1] = 0.0f;
|
|
hcolor[2] = 0.0f;
|
|
hcolor[3] = 0.33f;
|
|
} else { // if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_BLUE )
|
|
hcolor[0] = 0.0f;
|
|
hcolor[1] = 0.0f;
|
|
hcolor[2] = 1.0f;
|
|
hcolor[3] = 0.33f;
|
|
}
|
|
trap_R_SetColor( hcolor );
|
|
CG_DrawPic( x + xOffset, y, w, h, cgs.media.teamStatusBar );
|
|
trap_R_SetColor( NULL );
|
|
|
|
for (i = 0; i < count; i++) {
|
|
ci = cgs.clientinfo + sortedTeamPlayers[i];
|
|
if ( ci->infoValid && ci->team == cg.snap->ps.persistant[PERS_TEAM]) {
|
|
|
|
hcolor[0] = hcolor[1] = hcolor[2] = hcolor[3] = 1.0;
|
|
|
|
xx = x + TINYCHAR_WIDTH;
|
|
|
|
CG_DrawStringExt( xx + xOffset, y,
|
|
ci->name, hcolor, qfalse, qfalse,
|
|
TINYCHAR_WIDTH, TINYCHAR_HEIGHT, TEAM_OVERLAY_MAXNAME_WIDTH);
|
|
|
|
if (lwidth) {
|
|
p = CG_GetLocationString(CG_ConfigString(CS_LOCATIONS+ci->location));
|
|
if (!p || !*p)
|
|
p = "unknown";
|
|
len = CG_DrawStrlen(p);
|
|
if (len > lwidth)
|
|
len = lwidth;
|
|
|
|
// xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth +
|
|
// ((lwidth/2 - len/2) * TINYCHAR_WIDTH);
|
|
xx = x + TINYCHAR_WIDTH * 2 + TINYCHAR_WIDTH * pwidth;
|
|
CG_DrawStringExt( xx + xOffset, y,
|
|
p, hcolor, qfalse, qfalse, TINYCHAR_WIDTH, TINYCHAR_HEIGHT,
|
|
TEAM_OVERLAY_MAXLOCATION_WIDTH);
|
|
}
|
|
|
|
CG_GetColorForHealth( ci->health, ci->armor, hcolor );
|
|
|
|
Com_sprintf (st, sizeof(st), "%3i %3i", ci->health, ci->armor);
|
|
|
|
xx = x + TINYCHAR_WIDTH * 3 +
|
|
TINYCHAR_WIDTH * pwidth + TINYCHAR_WIDTH * lwidth;
|
|
|
|
CG_DrawStringExt( xx + xOffset, y,
|
|
st, hcolor, qfalse, qfalse,
|
|
TINYCHAR_WIDTH, TINYCHAR_HEIGHT, 0 );
|
|
|
|
// draw weapon icon
|
|
xx += TINYCHAR_WIDTH * 3;
|
|
|
|
if ( cg_weapons[ci->curWeapon].weaponIcon ) {
|
|
CG_DrawPic( xx + xOffset, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT,
|
|
cg_weapons[ci->curWeapon].weaponIcon );
|
|
} else {
|
|
CG_DrawPic( xx + xOffset, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT,
|
|
cgs.media.deferShader );
|
|
}
|
|
|
|
// Draw powerup icons
|
|
if (right) {
|
|
xx = x;
|
|
} else {
|
|
xx = x + w - TINYCHAR_WIDTH;
|
|
}
|
|
for (j = 0; j <= PW_NUM_POWERUPS; j++) {
|
|
if (ci->powerups & (1 << j)) {
|
|
|
|
item = BG_FindItemForPowerup( j );
|
|
|
|
if (item) {
|
|
CG_DrawPic( xx + xOffset, y, TINYCHAR_WIDTH, TINYCHAR_HEIGHT,
|
|
trap_R_RegisterShader( item->icon ) );
|
|
if (right) {
|
|
xx -= TINYCHAR_WIDTH;
|
|
} else {
|
|
xx += TINYCHAR_WIDTH;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
y += TINYCHAR_HEIGHT;
|
|
}
|
|
}
|
|
|
|
return ret_y;
|
|
//#endif
|
|
}
|
|
|
|
|
|
static void CG_DrawPowerupIcons(int y)
|
|
{
|
|
int j;
|
|
int ico_size = 64;
|
|
//int y = ico_size/2;
|
|
int xOffset = 0;
|
|
gitem_t *item;
|
|
|
|
#ifdef _XBOX
|
|
xOffset = -40;
|
|
#endif
|
|
|
|
if (!cg.snap)
|
|
{
|
|
return;
|
|
}
|
|
|
|
y += 16;
|
|
|
|
for (j = 0; j <= PW_NUM_POWERUPS; j++)
|
|
{
|
|
if (cg.snap->ps.powerups[j] > cg.time)
|
|
{
|
|
int secondsleft = (cg.snap->ps.powerups[j] - cg.time)/1000;
|
|
|
|
item = BG_FindItemForPowerup( j );
|
|
|
|
if (item)
|
|
{
|
|
int icoShader = 0;
|
|
if (cgs.gametype == GT_CTY && (j == PW_REDFLAG || j == PW_BLUEFLAG))
|
|
{
|
|
if (j == PW_REDFLAG)
|
|
{
|
|
icoShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_ys" );
|
|
}
|
|
else
|
|
{
|
|
icoShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_ys" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
icoShader = trap_R_RegisterShader( item->icon );
|
|
}
|
|
|
|
CG_DrawPic( (640-(ico_size*1.1)) + xOffset, y, ico_size, ico_size, icoShader );
|
|
|
|
y += ico_size;
|
|
|
|
if (j != PW_REDFLAG && j != PW_BLUEFLAG && secondsleft < 999)
|
|
{
|
|
UI_DrawProportionalString((640-(ico_size*1.1))+(ico_size/2) + xOffset, y-8, va("%i", secondsleft), UI_CENTER | UI_BIGFONT | UI_DROPSHADOW, colorTable[CT_WHITE]);
|
|
}
|
|
|
|
y += (ico_size/3);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawUpperRight
|
|
|
|
=====================
|
|
*/
|
|
static void CG_DrawUpperRight( void ) {
|
|
float y;
|
|
|
|
#ifdef _XBOX
|
|
y = 50;
|
|
#else
|
|
y = 0;
|
|
#endif
|
|
|
|
trap_R_SetColor( colorTable[CT_WHITE] );
|
|
|
|
if ( cgs.gametype >= GT_TEAM && cg_drawTeamOverlay.integer == 1 ) {
|
|
y = CG_DrawTeamOverlay( y, qtrue, qtrue );
|
|
}
|
|
if ( cg_drawSnapshot.integer ) {
|
|
y = CG_DrawSnapshot( y );
|
|
}
|
|
|
|
if ( cg_drawFPS.integer ) {
|
|
y = CG_DrawFPS( y );
|
|
}
|
|
if ( cg_drawTimer.integer ) {
|
|
y = CG_DrawTimer( y );
|
|
}
|
|
|
|
if ( ( cgs.gametype >= GT_TEAM || cg.predictedPlayerState.m_iVehicleNum )
|
|
&& cg_drawRadar.integer )
|
|
{//draw Radar in Siege mode or when in a vehicle of any kind
|
|
y = CG_DrawRadar ( y );
|
|
}
|
|
|
|
y = CG_DrawEnemyInfo ( y );
|
|
|
|
y = CG_DrawMiniScoreboard ( y );
|
|
|
|
CG_DrawPowerupIcons(y);
|
|
}
|
|
|
|
/*
|
|
===================
|
|
CG_DrawReward
|
|
===================
|
|
*/
|
|
#ifdef JK2AWARDS
|
|
static void CG_DrawReward( void ) {
|
|
float *color;
|
|
int i, count;
|
|
float x, y;
|
|
char buf[32];
|
|
|
|
if ( !cg_drawRewards.integer ) {
|
|
return;
|
|
}
|
|
|
|
color = CG_FadeColor( cg.rewardTime, REWARD_TIME );
|
|
if ( !color ) {
|
|
if (cg.rewardStack > 0) {
|
|
for(i = 0; i < cg.rewardStack; i++) {
|
|
cg.rewardSound[i] = cg.rewardSound[i+1];
|
|
cg.rewardShader[i] = cg.rewardShader[i+1];
|
|
cg.rewardCount[i] = cg.rewardCount[i+1];
|
|
}
|
|
cg.rewardTime = cg.time;
|
|
cg.rewardStack--;
|
|
color = CG_FadeColor( cg.rewardTime, REWARD_TIME );
|
|
trap_S_StartLocalSound(cg.rewardSound[0], CHAN_ANNOUNCER);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
trap_R_SetColor( color );
|
|
|
|
/*
|
|
count = cg.rewardCount[0]/10; // number of big rewards to draw
|
|
|
|
if (count) {
|
|
y = 4;
|
|
x = 320 - count * ICON_SIZE;
|
|
for ( i = 0 ; i < count ; i++ ) {
|
|
CG_DrawPic( x, y, (ICON_SIZE*2)-4, (ICON_SIZE*2)-4, cg.rewardShader[0] );
|
|
x += (ICON_SIZE*2);
|
|
}
|
|
}
|
|
|
|
count = cg.rewardCount[0] - count*10; // number of small rewards to draw
|
|
*/
|
|
|
|
if ( cg.rewardCount[0] >= 10 ) {
|
|
y = 56;
|
|
x = 320 - ICON_SIZE/2;
|
|
CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] );
|
|
Com_sprintf(buf, sizeof(buf), "%d", cg.rewardCount[0]);
|
|
x = ( SCREEN_WIDTH - SMALLCHAR_WIDTH * CG_DrawStrlen( buf ) ) / 2;
|
|
CG_DrawStringExt( x, y+ICON_SIZE, buf, color, qfalse, qtrue,
|
|
SMALLCHAR_WIDTH, SMALLCHAR_HEIGHT, 0 );
|
|
}
|
|
else {
|
|
|
|
count = cg.rewardCount[0];
|
|
|
|
y = 56;
|
|
x = 320 - count * ICON_SIZE/2;
|
|
for ( i = 0 ; i < count ; i++ ) {
|
|
CG_DrawPic( x, y, ICON_SIZE-4, ICON_SIZE-4, cg.rewardShader[0] );
|
|
x += ICON_SIZE;
|
|
}
|
|
}
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
LAGOMETER
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
#define LAG_SAMPLES 128
|
|
|
|
|
|
typedef struct {
|
|
int frameSamples[LAG_SAMPLES];
|
|
int frameCount;
|
|
int snapshotFlags[LAG_SAMPLES];
|
|
int snapshotSamples[LAG_SAMPLES];
|
|
int snapshotCount;
|
|
} lagometer_t;
|
|
|
|
lagometer_t lagometer;
|
|
|
|
/*
|
|
==============
|
|
CG_AddLagometerFrameInfo
|
|
|
|
Adds the current interpolate / extrapolate bar for this frame
|
|
==============
|
|
*/
|
|
void CG_AddLagometerFrameInfo( void ) {
|
|
int offset;
|
|
|
|
offset = cg.time - cg.latestSnapshotTime;
|
|
lagometer.frameSamples[ lagometer.frameCount & ( LAG_SAMPLES - 1) ] = offset;
|
|
lagometer.frameCount++;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CG_AddLagometerSnapshotInfo
|
|
|
|
Each time a snapshot is received, log its ping time and
|
|
the number of snapshots that were dropped before it.
|
|
|
|
Pass NULL for a dropped packet.
|
|
==============
|
|
*/
|
|
void CG_AddLagometerSnapshotInfo( snapshot_t *snap ) {
|
|
// dropped packet
|
|
if ( !snap ) {
|
|
lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = -1;
|
|
lagometer.snapshotCount++;
|
|
return;
|
|
}
|
|
|
|
// add this snapshot's info
|
|
lagometer.snapshotSamples[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->ping;
|
|
lagometer.snapshotFlags[ lagometer.snapshotCount & ( LAG_SAMPLES - 1) ] = snap->snapFlags;
|
|
lagometer.snapshotCount++;
|
|
}
|
|
|
|
/*
|
|
==============
|
|
CG_DrawDisconnect
|
|
|
|
Should we draw something differnet for long lag vs no packets?
|
|
==============
|
|
*/
|
|
static void CG_DrawDisconnect( void ) {
|
|
float x, y;
|
|
int cmdNum;
|
|
usercmd_t cmd;
|
|
const char *s;
|
|
int w; // bk010215 - FIXME char message[1024];
|
|
|
|
if (cg.mMapChange)
|
|
{
|
|
s = CG_GetStringEdString("MP_INGAME", "SERVER_CHANGING_MAPS"); // s = "Server Changing Maps";
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
CG_DrawBigString( 320 - w/2, 100, s, 1.0F);
|
|
|
|
s = CG_GetStringEdString("MP_INGAME", "PLEASE_WAIT"); // s = "Please wait...";
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
CG_DrawBigString( 320 - w/2, 200, s, 1.0F);
|
|
return;
|
|
}
|
|
|
|
// draw the phone jack if we are completely past our buffers
|
|
cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1;
|
|
trap_GetUserCmd( cmdNum, &cmd );
|
|
if ( cmd.serverTime <= cg.snap->ps.commandTime
|
|
|| cmd.serverTime > cg.time ) { // special check for map_restart // bk 0102165 - FIXME
|
|
return;
|
|
}
|
|
|
|
// also add text in center of screen
|
|
s = CG_GetStringEdString("MP_INGAME", "CONNECTION_INTERRUPTED"); // s = "Connection Interrupted"; // bk 010215 - FIXME
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
CG_DrawBigString( 320 - w/2, 100, s, 1.0F);
|
|
|
|
// blink the icon
|
|
if ( ( cg.time >> 9 ) & 1 ) {
|
|
return;
|
|
}
|
|
|
|
x = 640 - 48;
|
|
y = 480 - 48;
|
|
|
|
CG_DrawPic( x, y, 48, 48, trap_R_RegisterShader("gfx/2d/net.tga" ) );
|
|
}
|
|
|
|
|
|
#define MAX_LAGOMETER_PING 900
|
|
#define MAX_LAGOMETER_RANGE 300
|
|
|
|
/*
|
|
==============
|
|
CG_DrawLagometer
|
|
==============
|
|
*/
|
|
static void CG_DrawLagometer( void ) {
|
|
int a, x, y, i;
|
|
float v;
|
|
float ax, ay, aw, ah, mid, range;
|
|
int color;
|
|
float vscale;
|
|
|
|
if ( !cg_lagometer.integer || cgs.localServer ) {
|
|
CG_DrawDisconnect();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// draw the graph
|
|
//
|
|
x = 640 - 48;
|
|
y = 480 - 144;
|
|
|
|
trap_R_SetColor( NULL );
|
|
CG_DrawPic( x, y, 48, 48, cgs.media.lagometerShader );
|
|
|
|
ax = x;
|
|
ay = y;
|
|
aw = 48;
|
|
ah = 48;
|
|
|
|
color = -1;
|
|
range = ah / 3;
|
|
mid = ay + range;
|
|
|
|
vscale = range / MAX_LAGOMETER_RANGE;
|
|
|
|
// draw the frame interpoalte / extrapolate graph
|
|
for ( a = 0 ; a < aw ; a++ ) {
|
|
i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1);
|
|
v = lagometer.frameSamples[i];
|
|
v *= vscale;
|
|
if ( v > 0 ) {
|
|
if ( color != 1 ) {
|
|
color = 1;
|
|
trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] );
|
|
}
|
|
if ( v > range ) {
|
|
v = range;
|
|
}
|
|
trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
|
|
} else if ( v < 0 ) {
|
|
if ( color != 2 ) {
|
|
color = 2;
|
|
trap_R_SetColor( g_color_table[ColorIndex(COLOR_BLUE)] );
|
|
}
|
|
v = -v;
|
|
if ( v > range ) {
|
|
v = range;
|
|
}
|
|
trap_R_DrawStretchPic( ax + aw - a, mid, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
|
|
}
|
|
}
|
|
|
|
// draw the snapshot latency / drop graph
|
|
range = ah / 2;
|
|
vscale = range / MAX_LAGOMETER_PING;
|
|
|
|
for ( a = 0 ; a < aw ; a++ ) {
|
|
i = ( lagometer.snapshotCount - 1 - a ) & (LAG_SAMPLES - 1);
|
|
v = lagometer.snapshotSamples[i];
|
|
if ( v > 0 ) {
|
|
if ( lagometer.snapshotFlags[i] & SNAPFLAG_RATE_DELAYED ) {
|
|
if ( color != 5 ) {
|
|
color = 5; // YELLOW for rate delay
|
|
trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] );
|
|
}
|
|
} else {
|
|
if ( color != 3 ) {
|
|
color = 3;
|
|
trap_R_SetColor( g_color_table[ColorIndex(COLOR_GREEN)] );
|
|
}
|
|
}
|
|
v = v * vscale;
|
|
if ( v > range ) {
|
|
v = range;
|
|
}
|
|
trap_R_DrawStretchPic( ax + aw - a, ay + ah - v, 1, v, 0, 0, 0, 0, cgs.media.whiteShader );
|
|
} else if ( v < 0 ) {
|
|
if ( color != 4 ) {
|
|
color = 4; // RED for dropped snapshots
|
|
trap_R_SetColor( g_color_table[ColorIndex(COLOR_RED)] );
|
|
}
|
|
trap_R_DrawStretchPic( ax + aw - a, ay + ah - range, 1, range, 0, 0, 0, 0, cgs.media.whiteShader );
|
|
}
|
|
}
|
|
|
|
trap_R_SetColor( NULL );
|
|
|
|
if ( cg_nopredict.integer || cg_synchronousClients.integer ) {
|
|
CG_DrawBigString( ax, ay, "snc", 1.0 );
|
|
}
|
|
|
|
CG_DrawDisconnect();
|
|
}
|
|
|
|
void CG_DrawSiegeMessage( const char *str, int objectiveScreen )
|
|
{
|
|
// if (!( trap_Key_GetCatcher() & KEYCATCH_UI ))
|
|
{
|
|
trap_OpenUIMenu(UIMENU_CLOSEALL);
|
|
trap_Cvar_Set("cg_siegeMessage", str);
|
|
if (objectiveScreen)
|
|
{
|
|
trap_OpenUIMenu(UIMENU_SIEGEOBJECTIVES);
|
|
}
|
|
else
|
|
{
|
|
trap_OpenUIMenu(UIMENU_SIEGEMESSAGE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CG_DrawSiegeMessageNonMenu( const char *str )
|
|
{
|
|
char text[1024];
|
|
if (str[0]=='@')
|
|
{
|
|
trap_SP_GetStringTextString(str+1, text, sizeof(text));
|
|
str = text;
|
|
}
|
|
CG_CenterPrint(str, SCREEN_HEIGHT * 0.20, BIGCHAR_WIDTH);
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
CENTER PRINTING
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
==============
|
|
CG_CenterPrint
|
|
|
|
Called for important messages that should stay in the center of the screen
|
|
for a few moments
|
|
==============
|
|
*/
|
|
void CG_CenterPrint( const char *str, int y, int charWidth ) {
|
|
char *s;
|
|
|
|
Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) );
|
|
|
|
cg.centerPrintTime = cg.time;
|
|
cg.centerPrintY = y;
|
|
cg.centerPrintCharWidth = charWidth;
|
|
|
|
// count the number of lines for centering
|
|
cg.centerPrintLines = 1;
|
|
s = cg.centerPrint;
|
|
while( *s ) {
|
|
if (*s == '\n')
|
|
cg.centerPrintLines++;
|
|
s++;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===================
|
|
CG_DrawCenterString
|
|
===================
|
|
*/
|
|
static void CG_DrawCenterString( void ) {
|
|
char *start;
|
|
int l;
|
|
int x, y, w;
|
|
int h;
|
|
float *color;
|
|
const float scale = 1.0; //0.5
|
|
|
|
if ( !cg.centerPrintTime ) {
|
|
return;
|
|
}
|
|
|
|
color = CG_FadeColor( cg.centerPrintTime, 1000 * cg_centertime.value );
|
|
if ( !color ) {
|
|
return;
|
|
}
|
|
|
|
trap_R_SetColor( color );
|
|
|
|
start = cg.centerPrint;
|
|
|
|
y = cg.centerPrintY - cg.centerPrintLines * BIGCHAR_HEIGHT / 2;
|
|
|
|
while ( 1 ) {
|
|
char linebuffer[1024];
|
|
|
|
for ( l = 0; l < 50; l++ ) {
|
|
if ( !start[l] || start[l] == '\n' ) {
|
|
break;
|
|
}
|
|
linebuffer[l] = start[l];
|
|
}
|
|
linebuffer[l] = 0;
|
|
|
|
w = CG_Text_Width(linebuffer, scale, FONT_MEDIUM);
|
|
h = CG_Text_Height(linebuffer, scale, FONT_MEDIUM);
|
|
x = (SCREEN_WIDTH - w) / 2;
|
|
CG_Text_Paint(x, y + h, scale, color, linebuffer, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM);
|
|
y += h + 6;
|
|
|
|
while ( *start && ( *start != '\n' ) ) {
|
|
start++;
|
|
}
|
|
if ( !*start ) {
|
|
break;
|
|
}
|
|
start++;
|
|
}
|
|
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
================================================================================
|
|
|
|
CROSSHAIR
|
|
|
|
================================================================================
|
|
*/
|
|
|
|
#define HEALTH_WIDTH 50.0f
|
|
#define HEALTH_HEIGHT 5.0f
|
|
|
|
//see if we can draw some extra info on this guy based on our class
|
|
void CG_DrawSiegeInfo(centity_t *cent, float chX, float chY, float chW, float chH)
|
|
{
|
|
siegeExtended_t *se = &cg_siegeExtendedData[cent->currentState.number];
|
|
clientInfo_t *ci;
|
|
const char *configstring, *v;
|
|
siegeClass_t *siegeClass;
|
|
vec4_t aColor;
|
|
vec4_t bColor;
|
|
vec4_t cColor;
|
|
float x;
|
|
float y;
|
|
float percent;
|
|
int ammoMax;
|
|
|
|
assert(cent->currentState.number < MAX_CLIENTS);
|
|
|
|
if (se->lastUpdated > cg.time)
|
|
{ //strange, shouldn't happen
|
|
return;
|
|
}
|
|
|
|
if ((cg.time - se->lastUpdated) > 10000)
|
|
{ //if you haven't received a status update on this guy in 10 seconds, forget about it
|
|
return;
|
|
}
|
|
|
|
if (cent->currentState.eFlags & EF_DEAD)
|
|
{ //he's dead, don't display info on him
|
|
return;
|
|
}
|
|
|
|
if (cent->currentState.weapon != se->weapon)
|
|
{ //data is invalidated until it syncs back again
|
|
return;
|
|
}
|
|
|
|
ci = &cgs.clientinfo[cent->currentState.number];
|
|
if (ci->team != cg.predictedPlayerState.persistant[PERS_TEAM])
|
|
{ //not on the same team
|
|
return;
|
|
}
|
|
|
|
configstring = CG_ConfigString( cg.predictedPlayerState.clientNum + CS_PLAYERS );
|
|
v = Info_ValueForKey( configstring, "siegeclass" );
|
|
|
|
if (!v || !v[0])
|
|
{ //don't have siege class in info?
|
|
return;
|
|
}
|
|
|
|
siegeClass = BG_SiegeFindClassByName(v);
|
|
|
|
if (!siegeClass)
|
|
{ //invalid
|
|
return;
|
|
}
|
|
|
|
if (!(siegeClass->classflags & (1<<CFL_STATVIEWER)))
|
|
{ //doesn't really have the ability to see others' stats
|
|
return;
|
|
}
|
|
|
|
x = chX+((chW/2)-(HEALTH_WIDTH/2));
|
|
y = (chY+chH) + 8.0f;
|
|
percent = ((float)se->health/(float)se->maxhealth)*HEALTH_WIDTH;
|
|
|
|
//color of the bar
|
|
aColor[0] = 0.0f;
|
|
aColor[1] = 1.0f;
|
|
aColor[2] = 0.0f;
|
|
aColor[3] = 0.4f;
|
|
|
|
//color of the border
|
|
bColor[0] = 0.0f;
|
|
bColor[1] = 0.0f;
|
|
bColor[2] = 0.0f;
|
|
bColor[3] = 0.3f;
|
|
|
|
//color of greyed out "missing health"
|
|
cColor[0] = 0.5f;
|
|
cColor[1] = 0.5f;
|
|
cColor[2] = 0.5f;
|
|
cColor[3] = 0.4f;
|
|
|
|
//draw the background (black)
|
|
CG_DrawRect(x, y, HEALTH_WIDTH, HEALTH_HEIGHT, 1.0f, colorTable[CT_BLACK]);
|
|
|
|
//now draw the part to show how much health there is in the color specified
|
|
CG_FillRect(x+1.0f, y+1.0f, percent-1.0f, HEALTH_HEIGHT-1.0f, aColor);
|
|
|
|
//then draw the other part greyed out
|
|
CG_FillRect(x+percent, y+1.0f, HEALTH_WIDTH-percent-1.0f, HEALTH_HEIGHT-1.0f, cColor);
|
|
|
|
|
|
//now draw his ammo
|
|
ammoMax = ammoData[weaponData[cent->currentState.weapon].ammoIndex].max;
|
|
if ( (cent->currentState.eFlags & EF_DOUBLE_AMMO) )
|
|
{
|
|
ammoMax *= 2;
|
|
}
|
|
|
|
x = chX+((chW/2)-(HEALTH_WIDTH/2));
|
|
y = (chY+chH) + HEALTH_HEIGHT + 10.0f;
|
|
|
|
if (!weaponData[cent->currentState.weapon].energyPerShot &&
|
|
!weaponData[cent->currentState.weapon].altEnergyPerShot)
|
|
{ //a weapon that takes no ammo, so show full
|
|
percent = HEALTH_WIDTH;
|
|
}
|
|
else
|
|
{
|
|
percent = ((float)se->ammo/(float)ammoMax)*HEALTH_WIDTH;
|
|
}
|
|
|
|
//color of the bar
|
|
aColor[0] = 1.0f;
|
|
aColor[1] = 1.0f;
|
|
aColor[2] = 0.0f;
|
|
aColor[3] = 0.4f;
|
|
|
|
//color of the border
|
|
bColor[0] = 0.0f;
|
|
bColor[1] = 0.0f;
|
|
bColor[2] = 0.0f;
|
|
bColor[3] = 0.3f;
|
|
|
|
//color of greyed out "missing health"
|
|
cColor[0] = 0.5f;
|
|
cColor[1] = 0.5f;
|
|
cColor[2] = 0.5f;
|
|
cColor[3] = 0.4f;
|
|
|
|
//draw the background (black)
|
|
CG_DrawRect(x, y, HEALTH_WIDTH, HEALTH_HEIGHT, 1.0f, colorTable[CT_BLACK]);
|
|
|
|
//now draw the part to show how much health there is in the color specified
|
|
CG_FillRect(x+1.0f, y+1.0f, percent-1.0f, HEALTH_HEIGHT-1.0f, aColor);
|
|
|
|
//then draw the other part greyed out
|
|
CG_FillRect(x+percent, y+1.0f, HEALTH_WIDTH-percent-1.0f, HEALTH_HEIGHT-1.0f, cColor);
|
|
}
|
|
|
|
//draw the health bar based on current "health" and maxhealth
|
|
void CG_DrawHealthBar(centity_t *cent, float chX, float chY, float chW, float chH)
|
|
{
|
|
vec4_t aColor;
|
|
vec4_t bColor;
|
|
vec4_t cColor;
|
|
float x = chX+((chW/2)-(HEALTH_WIDTH/2));
|
|
float y = (chY+chH) + 8.0f;
|
|
float percent = ((float)cent->currentState.health/(float)cent->currentState.maxhealth)*HEALTH_WIDTH;
|
|
if (percent <= 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//color of the bar
|
|
if (!cent->currentState.teamowner || cgs.gametype < GT_TEAM)
|
|
{ //not owned by a team or teamplay
|
|
aColor[0] = 1.0f;
|
|
aColor[1] = 1.0f;
|
|
aColor[2] = 0.0f;
|
|
aColor[3] = 0.4f;
|
|
}
|
|
else if (cent->currentState.teamowner == cg.predictedPlayerState.persistant[PERS_TEAM])
|
|
{ //owned by my team
|
|
aColor[0] = 0.0f;
|
|
aColor[1] = 1.0f;
|
|
aColor[2] = 0.0f;
|
|
aColor[3] = 0.4f;
|
|
}
|
|
else
|
|
{ //hostile
|
|
aColor[0] = 1.0f;
|
|
aColor[1] = 0.0f;
|
|
aColor[2] = 0.0f;
|
|
aColor[3] = 0.4f;
|
|
}
|
|
|
|
//color of the border
|
|
bColor[0] = 0.0f;
|
|
bColor[1] = 0.0f;
|
|
bColor[2] = 0.0f;
|
|
bColor[3] = 0.3f;
|
|
|
|
//color of greyed out "missing health"
|
|
cColor[0] = 0.5f;
|
|
cColor[1] = 0.5f;
|
|
cColor[2] = 0.5f;
|
|
cColor[3] = 0.4f;
|
|
|
|
//draw the background (black)
|
|
CG_DrawRect(x, y, HEALTH_WIDTH, HEALTH_HEIGHT, 1.0f, colorTable[CT_BLACK]);
|
|
|
|
//now draw the part to show how much health there is in the color specified
|
|
CG_FillRect(x+1.0f, y+1.0f, percent-1.0f, HEALTH_HEIGHT-1.0f, aColor);
|
|
|
|
//then draw the other part greyed out
|
|
CG_FillRect(x+percent, y+1.0f, HEALTH_WIDTH-percent-1.0f, HEALTH_HEIGHT-1.0f, cColor);
|
|
}
|
|
|
|
//same routine (at least for now), draw progress of a "hack" or whatever
|
|
void CG_DrawHaqrBar(float chX, float chY, float chW, float chH)
|
|
{
|
|
vec4_t aColor;
|
|
vec4_t bColor;
|
|
vec4_t cColor;
|
|
float x = chX+((chW/2)-(HEALTH_WIDTH/2));
|
|
float y = (chY+chH) + 8.0f;
|
|
float percent = (((float)cg.predictedPlayerState.hackingTime-(float)cg.time)/(float)cg.predictedPlayerState.hackingBaseTime)*HEALTH_WIDTH;
|
|
|
|
if (percent > HEALTH_WIDTH ||
|
|
percent < 1.0f)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//color of the bar
|
|
aColor[0] = 1.0f;
|
|
aColor[1] = 1.0f;
|
|
aColor[2] = 0.0f;
|
|
aColor[3] = 0.4f;
|
|
|
|
//color of the border
|
|
bColor[0] = 0.0f;
|
|
bColor[1] = 0.0f;
|
|
bColor[2] = 0.0f;
|
|
bColor[3] = 0.3f;
|
|
|
|
//color of greyed out done area
|
|
cColor[0] = 0.5f;
|
|
cColor[1] = 0.5f;
|
|
cColor[2] = 0.5f;
|
|
cColor[3] = 0.1f;
|
|
|
|
//draw the background (black)
|
|
CG_DrawRect(x, y, HEALTH_WIDTH, HEALTH_HEIGHT, 1.0f, colorTable[CT_BLACK]);
|
|
|
|
//now draw the part to show how much health there is in the color specified
|
|
CG_FillRect(x+1.0f, y+1.0f, percent-1.0f, HEALTH_HEIGHT-1.0f, aColor);
|
|
|
|
//then draw the other part greyed out
|
|
CG_FillRect(x+percent, y+1.0f, HEALTH_WIDTH-percent-1.0f, HEALTH_HEIGHT-1.0f, cColor);
|
|
|
|
//draw the hacker icon
|
|
CG_DrawPic(x, y-HEALTH_WIDTH, HEALTH_WIDTH, HEALTH_WIDTH, cgs.media.hackerIconShader);
|
|
}
|
|
|
|
//generic timing bar
|
|
int cg_genericTimerBar = 0;
|
|
int cg_genericTimerDur = 0;
|
|
vec4_t cg_genericTimerColor;
|
|
#define CGTIMERBAR_H 50.0f
|
|
#define CGTIMERBAR_W 10.0f
|
|
#define CGTIMERBAR_X (SCREEN_WIDTH-CGTIMERBAR_W-120.0f)
|
|
#define CGTIMERBAR_Y (SCREEN_HEIGHT-CGTIMERBAR_H-20.0f)
|
|
void CG_DrawGenericTimerBar(void)
|
|
{
|
|
vec4_t aColor;
|
|
vec4_t bColor;
|
|
vec4_t cColor;
|
|
float x = CGTIMERBAR_X;
|
|
float y = CGTIMERBAR_Y;
|
|
float percent = ((float)(cg_genericTimerBar-cg.time)/(float)cg_genericTimerDur)*CGTIMERBAR_H;
|
|
|
|
if (percent > CGTIMERBAR_H)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (percent < 0.1f)
|
|
{
|
|
percent = 0.1f;
|
|
}
|
|
|
|
//color of the bar
|
|
aColor[0] = cg_genericTimerColor[0];
|
|
aColor[1] = cg_genericTimerColor[1];
|
|
aColor[2] = cg_genericTimerColor[2];
|
|
aColor[3] = cg_genericTimerColor[3];
|
|
|
|
//color of the border
|
|
bColor[0] = 0.0f;
|
|
bColor[1] = 0.0f;
|
|
bColor[2] = 0.0f;
|
|
bColor[3] = 0.3f;
|
|
|
|
//color of greyed out "missing fuel"
|
|
cColor[0] = 0.5f;
|
|
cColor[1] = 0.5f;
|
|
cColor[2] = 0.5f;
|
|
cColor[3] = 0.1f;
|
|
|
|
//draw the background (black)
|
|
CG_DrawRect(x, y, CGTIMERBAR_W, CGTIMERBAR_H, 1.0f, colorTable[CT_BLACK]);
|
|
|
|
//now draw the part to show how much health there is in the color specified
|
|
CG_FillRect(x+1.0f, y+1.0f+(CGTIMERBAR_H-percent), CGTIMERBAR_W-2.0f, CGTIMERBAR_H-1.0f-(CGTIMERBAR_H-percent), aColor);
|
|
|
|
//then draw the other part greyed out
|
|
CG_FillRect(x+1.0f, y+1.0f, CGTIMERBAR_W-2.0f, CGTIMERBAR_H-percent, cColor);
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawCrosshair
|
|
=================
|
|
*/
|
|
|
|
#ifdef _XBOX
|
|
int cg_crossHairStatus = 0;
|
|
#endif
|
|
|
|
float cg_crosshairPrevPosX = 0;
|
|
float cg_crosshairPrevPosY = 0;
|
|
#define CRAZY_CROSSHAIR_MAX_ERROR_X (100.0f*640.0f/480.0f)
|
|
#define CRAZY_CROSSHAIR_MAX_ERROR_Y (100.0f)
|
|
void CG_LerpCrosshairPos( float *x, float *y )
|
|
{
|
|
if ( cg_crosshairPrevPosX )
|
|
{//blend from old pos
|
|
float maxMove = 30.0f * ((float)cg.frametime/500.0f) * 640.0f/480.0f;
|
|
float xDiff = (*x - cg_crosshairPrevPosX);
|
|
if ( fabs(xDiff) > CRAZY_CROSSHAIR_MAX_ERROR_X )
|
|
{
|
|
maxMove = CRAZY_CROSSHAIR_MAX_ERROR_X;
|
|
}
|
|
if ( xDiff > maxMove )
|
|
{
|
|
*x = cg_crosshairPrevPosX + maxMove;
|
|
}
|
|
else if ( xDiff < -maxMove )
|
|
{
|
|
*x = cg_crosshairPrevPosX - maxMove;
|
|
}
|
|
}
|
|
cg_crosshairPrevPosX = *x;
|
|
|
|
if ( cg_crosshairPrevPosY )
|
|
{//blend from old pos
|
|
float maxMove = 30.0f * ((float)cg.frametime/500.0f);
|
|
float yDiff = (*y - cg_crosshairPrevPosY);
|
|
if ( fabs(yDiff) > CRAZY_CROSSHAIR_MAX_ERROR_Y )
|
|
{
|
|
maxMove = CRAZY_CROSSHAIR_MAX_ERROR_X;
|
|
}
|
|
if ( yDiff > maxMove )
|
|
{
|
|
*y = cg_crosshairPrevPosY + maxMove;
|
|
}
|
|
else if ( yDiff < -maxMove )
|
|
{
|
|
*y = cg_crosshairPrevPosY - maxMove;
|
|
}
|
|
}
|
|
cg_crosshairPrevPosY = *y;
|
|
}
|
|
|
|
vec3_t cg_crosshairPos={0,0,0};
|
|
static void CG_DrawCrosshair( vec3_t worldPoint, int chEntValid ) {
|
|
float w, h;
|
|
qhandle_t hShader = 0;
|
|
float f;
|
|
float x, y;
|
|
qboolean corona = qfalse;
|
|
vec4_t ecolor = {0,0,0,0};
|
|
centity_t *crossEnt = NULL;
|
|
float chX, chY;
|
|
|
|
#ifdef _XBOX
|
|
cg_crossHairStatus = 0;
|
|
#endif
|
|
|
|
if ( worldPoint )
|
|
{
|
|
VectorCopy( worldPoint, cg_crosshairPos );
|
|
}
|
|
|
|
if ( !cg_drawCrosshair.integer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cg.snap->ps.fallingToDeath)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.predictedPlayerState.zoomMode != 0 )
|
|
{//not while scoped
|
|
return;
|
|
}
|
|
|
|
/*
|
|
if ( cg_drawingRocketLockThisFrame
|
|
&& cg.snap->ps.m_iVehicleNum )
|
|
{//in vehicle, rocket lock-on replaces crosshair
|
|
return;
|
|
}
|
|
*/
|
|
|
|
if ( cg_crosshairHealth.integer )
|
|
{
|
|
vec4_t hcolor;
|
|
|
|
CG_ColorForHealth( hcolor );
|
|
trap_R_SetColor( hcolor );
|
|
}
|
|
else
|
|
{
|
|
//set color based on what kind of ent is under crosshair
|
|
if ( cg.crosshairClientNum >= ENTITYNUM_WORLD )
|
|
{
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
//rwwFIXMEFIXME: Write this a different way, it's getting a bit too sloppy looking
|
|
else if (chEntValid &&
|
|
(cg_entities[cg.crosshairClientNum].currentState.number < MAX_CLIENTS ||
|
|
cg_entities[cg.crosshairClientNum].currentState.eType == ET_NPC ||
|
|
cg_entities[cg.crosshairClientNum].currentState.shouldtarget ||
|
|
cg_entities[cg.crosshairClientNum].currentState.health || //always show ents with health data under crosshair
|
|
(cg_entities[cg.crosshairClientNum].currentState.eType == ET_MOVER && cg_entities[cg.crosshairClientNum].currentState.bolt1 && cg.predictedPlayerState.weapon == WP_SABER) ||
|
|
(cg_entities[cg.crosshairClientNum].currentState.eType == ET_MOVER && cg_entities[cg.crosshairClientNum].currentState.teamowner)))
|
|
{
|
|
crossEnt = &cg_entities[cg.crosshairClientNum];
|
|
|
|
if ( crossEnt->currentState.powerups & (1 <<PW_CLOAKED) )
|
|
{ //don't show up for cloaked guys
|
|
ecolor[0] = 1.0;//R
|
|
ecolor[1] = 1.0;//G
|
|
ecolor[2] = 1.0;//B
|
|
}
|
|
else if ( crossEnt->currentState.number < MAX_CLIENTS )
|
|
{
|
|
if (cgs.gametype >= GT_TEAM &&
|
|
cgs.clientinfo[crossEnt->currentState.number].team == cgs.clientinfo[cg.snap->ps.clientNum].team )
|
|
{
|
|
//Allies are green
|
|
ecolor[0] = 0.0;//R
|
|
ecolor[1] = 1.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
}
|
|
else
|
|
{
|
|
if (cgs.gametype == GT_POWERDUEL &&
|
|
cgs.clientinfo[crossEnt->currentState.number].duelTeam == cgs.clientinfo[cg.snap->ps.clientNum].duelTeam)
|
|
{ //on the same duel team in powerduel, so he's a friend
|
|
ecolor[0] = 0.0;//R
|
|
ecolor[1] = 1.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
}
|
|
else
|
|
{ //Enemies are red
|
|
ecolor[0] = 1.0;//R
|
|
ecolor[1] = 0.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
#ifdef _XBOX
|
|
cg_crossHairStatus = 1;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
if (cg.snap->ps.duelInProgress)
|
|
{
|
|
if (crossEnt->currentState.number != cg.snap->ps.duelIndex)
|
|
{ //grey out crosshair for everyone but your foe if you're in a duel
|
|
ecolor[0] = 0.4;
|
|
ecolor[1] = 0.4;
|
|
ecolor[2] = 0.4;
|
|
}
|
|
}
|
|
else if (crossEnt->currentState.bolt1)
|
|
{ //this fellow is in a duel. We just checked if we were in a duel above, so
|
|
//this means we aren't and he is. Which of course means our crosshair greys out over him.
|
|
ecolor[0] = 0.4;
|
|
ecolor[1] = 0.4;
|
|
ecolor[2] = 0.4;
|
|
}
|
|
}
|
|
else if (crossEnt->currentState.shouldtarget || crossEnt->currentState.eType == ET_NPC)
|
|
{
|
|
//VectorCopy( crossEnt->startRGBA, ecolor );
|
|
if ( !ecolor[0] && !ecolor[1] && !ecolor[2] )
|
|
{
|
|
// We really don't want black, so set it to yellow
|
|
ecolor[0] = 1.0F;//R
|
|
ecolor[1] = 0.8F;//G
|
|
ecolor[2] = 0.3F;//B
|
|
}
|
|
|
|
if (crossEnt->currentState.eType == ET_NPC)
|
|
{
|
|
int plTeam;
|
|
if (cgs.gametype == GT_SIEGE)
|
|
{
|
|
plTeam = cg.predictedPlayerState.persistant[PERS_TEAM];
|
|
}
|
|
else
|
|
{
|
|
plTeam = NPCTEAM_PLAYER;
|
|
}
|
|
|
|
if ( crossEnt->currentState.powerups & (1 <<PW_CLOAKED) )
|
|
{
|
|
ecolor[0] = 1.0;//R
|
|
ecolor[1] = 1.0;//G
|
|
ecolor[2] = 1.0;//B
|
|
}
|
|
else if ( !crossEnt->currentState.teamowner )
|
|
{ //not on a team
|
|
if (!crossEnt->currentState.teamowner ||
|
|
crossEnt->currentState.NPC_class == CLASS_VEHICLE)
|
|
{ //neutral
|
|
if (crossEnt->currentState.owner < MAX_CLIENTS)
|
|
{ //base color on who is pilotting this thing
|
|
clientInfo_t *ci = &cgs.clientinfo[crossEnt->currentState.owner];
|
|
|
|
if (cgs.gametype >= GT_TEAM && ci->team == cg.predictedPlayerState.persistant[PERS_TEAM])
|
|
{ //friendly
|
|
ecolor[0] = 0.0;//R
|
|
ecolor[1] = 1.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
}
|
|
else
|
|
{ //hostile
|
|
ecolor[0] = 1.0;//R
|
|
ecolor[1] = 0.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
#ifdef _XBOX
|
|
cg_crossHairStatus = 1;
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{ //unmanned
|
|
ecolor[0] = 1.0;//R
|
|
ecolor[1] = 1.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ecolor[0] = 1.0;//R
|
|
ecolor[1] = 0.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
#ifdef _XBOX
|
|
cg_crossHairStatus = 1;
|
|
#endif
|
|
}
|
|
}
|
|
else if ( crossEnt->currentState.teamowner != plTeam )
|
|
{// on enemy team
|
|
ecolor[0] = 1.0;//R
|
|
ecolor[1] = 0.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
#ifdef _XBOX
|
|
cg_crossHairStatus = 1;
|
|
#endif
|
|
}
|
|
else
|
|
{ //a friend
|
|
ecolor[0] = 0.0;//R
|
|
ecolor[1] = 1.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
}
|
|
}
|
|
else if ( crossEnt->currentState.teamowner == TEAM_RED
|
|
|| crossEnt->currentState.teamowner == TEAM_BLUE )
|
|
{
|
|
if (cgs.gametype < GT_TEAM)
|
|
{ //not teamplay, just neutral then
|
|
ecolor[0] = 1.0;//R
|
|
ecolor[1] = 1.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
}
|
|
else if ( crossEnt->currentState.teamowner != cgs.clientinfo[cg.snap->ps.clientNum].team )
|
|
{ //on the enemy team
|
|
ecolor[0] = 1.0;//R
|
|
ecolor[1] = 0.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
#ifdef _XBOX
|
|
cg_crossHairStatus = 1;
|
|
#endif
|
|
}
|
|
else
|
|
{ //on my team
|
|
ecolor[0] = 0.0;//R
|
|
ecolor[1] = 1.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
}
|
|
}
|
|
else if (crossEnt->currentState.owner == cg.snap->ps.clientNum ||
|
|
(cgs.gametype >= GT_TEAM && crossEnt->currentState.teamowner == cgs.clientinfo[cg.snap->ps.clientNum].team))
|
|
{
|
|
ecolor[0] = 0.0;//R
|
|
ecolor[1] = 1.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
}
|
|
else if (crossEnt->currentState.teamowner == 16 ||
|
|
(cgs.gametype >= GT_TEAM && crossEnt->currentState.teamowner && crossEnt->currentState.teamowner != cgs.clientinfo[cg.snap->ps.clientNum].team))
|
|
{
|
|
ecolor[0] = 1.0;//R
|
|
ecolor[1] = 0.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
#ifdef _XBOX
|
|
cg_crossHairStatus = 1;
|
|
#endif
|
|
}
|
|
}
|
|
else if (crossEnt->currentState.eType == ET_MOVER && crossEnt->currentState.bolt1 && cg.predictedPlayerState.weapon == WP_SABER)
|
|
{ //can push/pull this mover. Only show it if we're using the saber.
|
|
ecolor[0] = 0.2f;
|
|
ecolor[1] = 0.5f;
|
|
ecolor[2] = 1.0f;
|
|
|
|
corona = qtrue;
|
|
}
|
|
else if (crossEnt->currentState.eType == ET_MOVER && crossEnt->currentState.teamowner)
|
|
{ //a team owns this - if it's my team green, if not red, if not teamplay then yellow
|
|
if (cgs.gametype < GT_TEAM)
|
|
{
|
|
ecolor[0] = 1.0;//R
|
|
ecolor[1] = 1.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
}
|
|
else if (cg.predictedPlayerState.persistant[PERS_TEAM] != crossEnt->currentState.teamowner)
|
|
{ //not my team
|
|
ecolor[0] = 1.0;//R
|
|
ecolor[1] = 0.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
#ifdef _XBOX
|
|
cg_crossHairStatus = 1;
|
|
#endif
|
|
}
|
|
else
|
|
{ //my team
|
|
ecolor[0] = 0.0;//R
|
|
ecolor[1] = 1.0;//G
|
|
ecolor[2] = 0.0;//B
|
|
}
|
|
}
|
|
else if (crossEnt->currentState.health)
|
|
{
|
|
if (!crossEnt->currentState.teamowner || cgs.gametype < GT_TEAM)
|
|
{ //not owned by a team or teamplay
|
|
ecolor[0] = 1.0f;
|
|
ecolor[1] = 1.0f;
|
|
ecolor[2] = 0.0f;
|
|
}
|
|
else if (crossEnt->currentState.teamowner == cg.predictedPlayerState.persistant[PERS_TEAM])
|
|
{ //owned by my team
|
|
ecolor[0] = 0.0f;
|
|
ecolor[1] = 1.0f;
|
|
ecolor[2] = 0.0f;
|
|
}
|
|
else
|
|
{ //hostile
|
|
ecolor[0] = 1.0f;
|
|
ecolor[1] = 0.0f;
|
|
ecolor[2] = 0.0f;
|
|
#ifdef _XBOX
|
|
cg_crossHairStatus = 1;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
ecolor[3] = 1.0;
|
|
|
|
trap_R_SetColor( ecolor );
|
|
}
|
|
}
|
|
|
|
if ( cg.predictedPlayerState.m_iVehicleNum )
|
|
{//I'm in a vehicle
|
|
centity_t *vehCent = &cg_entities[cg.predictedPlayerState.m_iVehicleNum];
|
|
if ( vehCent
|
|
&& vehCent->m_pVehicle
|
|
&& vehCent->m_pVehicle->m_pVehicleInfo
|
|
&& vehCent->m_pVehicle->m_pVehicleInfo->crosshairShaderHandle )
|
|
{
|
|
hShader = vehCent->m_pVehicle->m_pVehicleInfo->crosshairShaderHandle;
|
|
}
|
|
//bigger by default
|
|
w = cg_crosshairSize.value*2.0f;
|
|
h = w;
|
|
}
|
|
else
|
|
{
|
|
w = h = cg_crosshairSize.value;
|
|
}
|
|
|
|
// pulse the size of the crosshair when picking up items
|
|
f = cg.time - cg.itemPickupBlendTime;
|
|
if ( f > 0 && f < ITEM_BLOB_TIME ) {
|
|
f /= ITEM_BLOB_TIME;
|
|
w *= ( 1 + f );
|
|
h *= ( 1 + f );
|
|
}
|
|
|
|
if ( worldPoint && VectorLength( worldPoint ) )
|
|
{
|
|
if ( !CG_WorldCoordToScreenCoordFloat( worldPoint, &x, &y ) )
|
|
{//off screen, don't draw it
|
|
return;
|
|
}
|
|
//CG_LerpCrosshairPos( &x, &y );
|
|
x -= 320;
|
|
y -= 240;
|
|
}
|
|
else
|
|
{
|
|
x = cg_crosshairX.integer;
|
|
y = cg_crosshairY.integer;
|
|
}
|
|
|
|
if ( !hShader )
|
|
{
|
|
hShader = cgs.media.crosshairShader[ cg_drawCrosshair.integer % NUM_CROSSHAIRS ];
|
|
}
|
|
|
|
chX = x + cg.refdef.x + 0.5 * (640 - w);
|
|
chY = y + cg.refdef.y + 0.5 * (480 - h);
|
|
trap_R_DrawStretchPic( chX, chY, w, h, 0, 0, 1, 1, hShader );
|
|
|
|
//draw a health bar directly under the crosshair if we're looking at something
|
|
//that takes damage
|
|
if (crossEnt &&
|
|
crossEnt->currentState.maxhealth)
|
|
{
|
|
CG_DrawHealthBar(crossEnt, chX, chY, w, h);
|
|
chY += HEALTH_HEIGHT*2;
|
|
}
|
|
else if (crossEnt && crossEnt->currentState.number < MAX_CLIENTS)
|
|
{
|
|
if (cgs.gametype == GT_SIEGE)
|
|
{
|
|
CG_DrawSiegeInfo(crossEnt, chX, chY, w, h);
|
|
chY += HEALTH_HEIGHT*4;
|
|
}
|
|
if (cg.crosshairVehNum && cg.time == cg.crosshairVehTime)
|
|
{ //it was in the crosshair this frame
|
|
centity_t *hisVeh = &cg_entities[cg.crosshairVehNum];
|
|
|
|
if (hisVeh->currentState.eType == ET_NPC &&
|
|
hisVeh->currentState.NPC_class == CLASS_VEHICLE &&
|
|
hisVeh->currentState.maxhealth &&
|
|
hisVeh->m_pVehicle)
|
|
{ //draw the health for this vehicle
|
|
CG_DrawHealthBar(hisVeh, chX, chY, w, h);
|
|
chY += HEALTH_HEIGHT*2;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cg.predictedPlayerState.hackingTime)
|
|
{ //hacking something
|
|
CG_DrawHaqrBar(chX, chY, w, h);
|
|
}
|
|
|
|
if (cg_genericTimerBar > cg.time)
|
|
{ //draw generic timing bar, can be used for whatever
|
|
CG_DrawGenericTimerBar();
|
|
}
|
|
|
|
if ( corona ) // drawing extra bits
|
|
{
|
|
ecolor[3] = 0.5f;
|
|
ecolor[0] = ecolor[1] = ecolor[2] = (1 - ecolor[3]) * ( sin( cg.time * 0.001f ) * 0.08f + 0.35f ); // don't draw full color
|
|
ecolor[3] = 1.0f;
|
|
|
|
trap_R_SetColor( ecolor );
|
|
|
|
w *= 2.0f;
|
|
h *= 2.0f;
|
|
|
|
trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (640 - w),
|
|
y + cg.refdef.y + 0.5 * (480 - h),
|
|
w, h, 0, 0, 1, 1, cgs.media.forceCoronaShader );
|
|
}
|
|
}
|
|
|
|
qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y)
|
|
{
|
|
float xcenter, ycenter;
|
|
vec3_t local, transformed;
|
|
vec3_t vfwd;
|
|
vec3_t vright;
|
|
vec3_t vup;
|
|
float xzi;
|
|
float yzi;
|
|
|
|
// xcenter = cg.refdef.width / 2;//gives screen coords adjusted for resolution
|
|
// ycenter = cg.refdef.height / 2;//gives screen coords adjusted for resolution
|
|
|
|
//NOTE: did it this way because most draw functions expect virtual 640x480 coords
|
|
// and adjust them for current resolution
|
|
xcenter = 640.0f / 2.0f;//gives screen coords in virtual 640x480, to be adjusted when drawn
|
|
ycenter = 480.0f / 2.0f;//gives screen coords in virtual 640x480, to be adjusted when drawn
|
|
|
|
AngleVectors (cg.refdef.viewangles, vfwd, vright, vup);
|
|
|
|
VectorSubtract (worldCoord, cg.refdef.vieworg, local);
|
|
|
|
transformed[0] = DotProduct(local,vright);
|
|
transformed[1] = DotProduct(local,vup);
|
|
transformed[2] = DotProduct(local,vfwd);
|
|
|
|
// Make sure Z is not negative.
|
|
if(transformed[2] < 0.01f)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
xzi = xcenter / transformed[2] * (96.0f/cg.refdef.fov_x);
|
|
yzi = ycenter / transformed[2] * (102.0f/cg.refdef.fov_y);
|
|
|
|
*x = xcenter + xzi * transformed[0];
|
|
*y = ycenter - yzi * transformed[1];
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
qboolean CG_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y )
|
|
{
|
|
float xF, yF;
|
|
qboolean retVal = CG_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF );
|
|
*x = (int)xF;
|
|
*y = (int)yF;
|
|
return retVal;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CG_SaberClashFlare
|
|
====================
|
|
*/
|
|
int cg_saberFlashTime = 0;
|
|
vec3_t cg_saberFlashPos = {0, 0, 0};
|
|
void CG_SaberClashFlare( void )
|
|
{
|
|
int t, maxTime = 150;
|
|
vec3_t dif;
|
|
vec3_t color;
|
|
int x,y;
|
|
float v, len;
|
|
trace_t tr;
|
|
|
|
t = cg.time - cg_saberFlashTime;
|
|
|
|
if ( t <= 0 || t >= maxTime )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Don't do clashes for things that are behind us
|
|
VectorSubtract( cg_saberFlashPos, cg.refdef.vieworg, dif );
|
|
|
|
if ( DotProduct( dif, cg.refdef.viewaxis[0] ) < 0.2 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, cg_saberFlashPos, -1, CONTENTS_SOLID );
|
|
|
|
if ( tr.fraction < 1.0f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
len = VectorNormalize( dif );
|
|
|
|
// clamp to a known range
|
|
/*
|
|
if ( len > 800 )
|
|
{
|
|
len = 800;
|
|
}
|
|
*/
|
|
if ( len > 1200 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
v = ( 1.0f - ((float)t / maxTime )) * ((1.0f - ( len / 800.0f )) * 2.0f + 0.35f);
|
|
if (v < 0.001f)
|
|
{
|
|
v = 0.001f;
|
|
}
|
|
|
|
CG_WorldCoordToScreenCoord( cg_saberFlashPos, &x, &y );
|
|
|
|
VectorSet( color, 0.8f, 0.8f, 0.8f );
|
|
trap_R_SetColor( color );
|
|
|
|
CG_DrawPic( x - ( v * 300 ), y - ( v * 300 ),
|
|
v * 600, v * 600,
|
|
trap_R_RegisterShader( "gfx/effects/saberFlare" ));
|
|
}
|
|
|
|
void CG_DottedLine( float x1, float y1, float x2, float y2, float dotSize, int numDots, vec4_t color, float alpha )
|
|
{
|
|
float x, y, xDiff, yDiff, xStep, yStep;
|
|
vec4_t colorRGBA;
|
|
int dotNum = 0;
|
|
|
|
VectorCopy4( color, colorRGBA );
|
|
colorRGBA[3] = alpha;
|
|
|
|
trap_R_SetColor( colorRGBA );
|
|
|
|
xDiff = x2-x1;
|
|
yDiff = y2-y1;
|
|
xStep = xDiff/(float)numDots;
|
|
yStep = yDiff/(float)numDots;
|
|
|
|
for ( dotNum = 0; dotNum < numDots; dotNum++ )
|
|
{
|
|
x = x1 + (xStep*dotNum) - (dotSize*0.5f);
|
|
y = y1 + (yStep*dotNum) - (dotSize*0.5f);
|
|
|
|
CG_DrawPic( x, y, dotSize, dotSize, cgs.media.whiteShader );
|
|
}
|
|
}
|
|
|
|
void CG_BracketEntity( centity_t *cent, float radius )
|
|
{
|
|
trace_t tr;
|
|
vec3_t dif;
|
|
float len, size, lineLength, lineWidth;
|
|
float x, y;
|
|
clientInfo_t *local;
|
|
qboolean isEnemy = qfalse;
|
|
|
|
VectorSubtract( cent->lerpOrigin, cg.refdef.vieworg, dif );
|
|
len = VectorNormalize( dif );
|
|
|
|
if ( cg.crosshairClientNum != cent->currentState.clientNum
|
|
&& (!cg.snap||cg.snap->ps.rocketLockIndex!= cent->currentState.clientNum) )
|
|
{//if they're the entity you're locking onto or under your crosshair, always draw bracket
|
|
//Hmm... for now, if they're closer than 2000, don't bracket?
|
|
if ( len < 2000.0f )
|
|
{
|
|
return;
|
|
}
|
|
|
|
CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, cent->lerpOrigin, -1, CONTENTS_OPAQUE );
|
|
|
|
//don't bracket if can't see them
|
|
if ( tr.fraction < 1.0f )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ( !CG_WorldCoordToScreenCoordFloat(cent->lerpOrigin, &x, &y) )
|
|
{//off-screen, don't draw it
|
|
return;
|
|
}
|
|
|
|
//just to see if it's centered
|
|
//CG_DrawPic( x-2, y-2, 4, 4, cgs.media.whiteShader );
|
|
|
|
local = &cgs.clientinfo[cg.snap->ps.clientNum];
|
|
if ( cent->currentState.m_iVehicleNum //vehicle has a driver
|
|
&& cgs.clientinfo[ cent->currentState.m_iVehicleNum-1 ].infoValid )
|
|
{
|
|
if ( cgs.gametype < GT_TEAM )
|
|
{//ffa?
|
|
isEnemy = qtrue;
|
|
trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] );
|
|
}
|
|
else if ( cgs.clientinfo[ cent->currentState.m_iVehicleNum-1 ].team == local->team )
|
|
{
|
|
trap_R_SetColor ( g_color_table[ColorIndex(COLOR_GREEN)] );
|
|
}
|
|
else
|
|
{
|
|
isEnemy = qtrue;
|
|
trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] );
|
|
}
|
|
}
|
|
else if ( cent->currentState.teamowner )
|
|
{
|
|
if ( cgs.gametype < GT_TEAM )
|
|
{//ffa?
|
|
isEnemy = qtrue;
|
|
trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] );
|
|
}
|
|
else if ( cent->currentState.teamowner != cg.predictedPlayerState.persistant[PERS_TEAM] )
|
|
{// on enemy team
|
|
isEnemy = qtrue;
|
|
trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] );
|
|
}
|
|
else
|
|
{ //a friend
|
|
trap_R_SetColor ( g_color_table[ColorIndex(COLOR_GREEN)] );
|
|
}
|
|
}
|
|
else
|
|
{//FIXME: if we want to ever bracket anything besides vehicles (like siege objectives we want to blow up), we should handle the coloring here
|
|
trap_R_SetColor ( NULL );
|
|
}
|
|
|
|
if ( len <= 1.0f )
|
|
{//super-close, max out at 400 times radius (which is HUGE)
|
|
size = radius*400.0f;
|
|
}
|
|
else
|
|
{//scale by dist
|
|
size = radius*(400.0f/len);
|
|
}
|
|
|
|
if ( size < 1.0f )
|
|
{
|
|
size = 1.0f;
|
|
}
|
|
|
|
//length scales with dist
|
|
lineLength = (size*0.1f);
|
|
if ( lineLength < 0.5f )
|
|
{//always visible
|
|
lineLength = 0.5f;
|
|
}
|
|
//always visible width
|
|
lineWidth = 1.0f;
|
|
|
|
x -= (size*0.5f);
|
|
y -= (size*0.5f);
|
|
|
|
/*
|
|
if ( x >= 0 && x <= 640
|
|
&& y >= 0 && y <= 480 )
|
|
*/
|
|
{//brackets would be drawn on the screen, so draw them
|
|
//upper left corner
|
|
//horz
|
|
CG_DrawPic( x, y, lineLength, lineWidth, cgs.media.whiteShader );
|
|
//vert
|
|
CG_DrawPic( x, y, lineWidth, lineLength, cgs.media.whiteShader );
|
|
//upper right corner
|
|
//horz
|
|
CG_DrawPic( x+size-lineLength, y, lineLength, lineWidth, cgs.media.whiteShader );
|
|
//vert
|
|
CG_DrawPic( x+size-lineWidth, y, lineWidth, lineLength, cgs.media.whiteShader );
|
|
//lower left corner
|
|
//horz
|
|
CG_DrawPic( x, y+size-lineWidth, lineLength, lineWidth, cgs.media.whiteShader );
|
|
//vert
|
|
CG_DrawPic( x, y+size-lineLength, lineWidth, lineLength, cgs.media.whiteShader );
|
|
//lower right corner
|
|
//horz
|
|
CG_DrawPic( x+size-lineLength, y+size-lineWidth, lineLength, lineWidth, cgs.media.whiteShader );
|
|
//vert
|
|
CG_DrawPic( x+size-lineWidth, y+size-lineLength, lineWidth, lineLength, cgs.media.whiteShader );
|
|
}
|
|
//Lead Indicator...
|
|
if ( cg_drawVehLeadIndicator.integer )
|
|
{//draw the lead indicator
|
|
if ( isEnemy )
|
|
{//an enemy object
|
|
if ( cent->currentState.NPC_class == CLASS_VEHICLE )
|
|
{//enemy vehicle
|
|
if ( !VectorCompare( cent->currentState.pos.trDelta, vec3_origin ) )
|
|
{//enemy vehicle is moving
|
|
if ( cg.predictedPlayerState.m_iVehicleNum )
|
|
{//I'm in a vehicle
|
|
centity_t *veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum];
|
|
if ( veh //vehicle cent
|
|
&& veh->m_pVehicle//vehicle
|
|
&& veh->m_pVehicle->m_pVehicleInfo//vehicle stats
|
|
&& veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID > VEH_WEAPON_BASE )//valid vehicle weapon
|
|
{
|
|
vehWeaponInfo_t *vehWeapon = &g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID];
|
|
if ( vehWeapon
|
|
&& vehWeapon->bIsProjectile//primary weapon's shot is a projectile
|
|
&& !vehWeapon->bHasGravity//primary weapon's shot is not affected by gravity
|
|
&& !vehWeapon->fHoming//primary weapon's shot is not homing
|
|
&& vehWeapon->fSpeed )//primary weapon's shot has speed
|
|
{//our primary weapon's projectile has a speed
|
|
vec3_t vehDiff, vehLeadPos;
|
|
float vehDist, eta;
|
|
float leadX, leadY;
|
|
|
|
VectorSubtract( cent->lerpOrigin, cg.predictedVehicleState.origin, vehDiff );
|
|
vehDist = VectorNormalize( vehDiff );
|
|
eta = (vehDist/vehWeapon->fSpeed);//how many seconds it would take for my primary weapon's projectile to get from my ship to theirs
|
|
//now extrapolate their position that number of seconds into the future based on their velocity
|
|
VectorMA( cent->lerpOrigin, eta, cent->currentState.pos.trDelta, vehLeadPos );
|
|
//now we have where we should be aiming at, project that onto the screen at a 2D co-ord
|
|
if ( !CG_WorldCoordToScreenCoordFloat(cent->lerpOrigin, &x, &y) )
|
|
{//off-screen, don't draw it
|
|
return;
|
|
}
|
|
if ( !CG_WorldCoordToScreenCoordFloat(vehLeadPos, &leadX, &leadY) )
|
|
{//off-screen, don't draw it
|
|
//just draw the line
|
|
CG_DottedLine( x, y, leadX, leadY, 1, 10, g_color_table[ColorIndex(COLOR_RED)], 0.5f );
|
|
return;
|
|
}
|
|
//draw a line from the ship's cur pos to the lead pos
|
|
CG_DottedLine( x, y, leadX, leadY, 1, 10, g_color_table[ColorIndex(COLOR_RED)], 0.5f );
|
|
//now draw the lead indicator
|
|
trap_R_SetColor ( g_color_table[ColorIndex(COLOR_RED)] );
|
|
CG_DrawPic( leadX-8, leadY-8, 16, 16, trap_R_RegisterShader( "gfx/menus/radar/lead" ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
qboolean CG_InFighter( void )
|
|
{
|
|
if ( cg.predictedPlayerState.m_iVehicleNum )
|
|
{//I'm in a vehicle
|
|
centity_t *vehCent = &cg_entities[cg.predictedPlayerState.m_iVehicleNum];
|
|
if ( vehCent
|
|
&& vehCent->m_pVehicle
|
|
&& vehCent->m_pVehicle->m_pVehicleInfo
|
|
&& vehCent->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER )
|
|
{//I'm in a fighter
|
|
return qtrue;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
qboolean CG_InATST( void )
|
|
{
|
|
if ( cg.predictedPlayerState.m_iVehicleNum )
|
|
{//I'm in a vehicle
|
|
centity_t *vehCent = &cg_entities[cg.predictedPlayerState.m_iVehicleNum];
|
|
if ( vehCent
|
|
&& vehCent->m_pVehicle
|
|
&& vehCent->m_pVehicle->m_pVehicleInfo
|
|
&& vehCent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER )
|
|
{//I'm in an atst
|
|
return qtrue;
|
|
}
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
void CG_DrawBracketedEntities( void )
|
|
{
|
|
int i;
|
|
for ( i = 0; i < cg.bracketedEntityCount; i++ )
|
|
{
|
|
centity_t *cent = &cg_entities[cg.bracketedEntities[i]];
|
|
CG_BracketEntity( cent, CG_RadiusForCent( cent ) );
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
static void CG_DrawHolocronIcons(void)
|
|
//--------------------------------------------------------------
|
|
{
|
|
int icon_size = 40;
|
|
int i = 0;
|
|
int startx = 10;
|
|
int starty = 10;//SCREEN_HEIGHT - icon_size*3;
|
|
|
|
int endx = icon_size;
|
|
int endy = icon_size;
|
|
|
|
if (cg.snap->ps.zoomMode)
|
|
{ //don't display over zoom mask
|
|
return;
|
|
}
|
|
|
|
if (cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR)
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
if (cg.snap->ps.holocronBits & (1 << forcePowerSorted[i]))
|
|
{
|
|
CG_DrawPic( startx, starty, endx, endy, cgs.media.forcePowerIcons[forcePowerSorted[i]]);
|
|
starty += (icon_size+2); //+2 for spacing
|
|
if ((starty+icon_size) >= SCREEN_HEIGHT-80)
|
|
{
|
|
starty = 10;//SCREEN_HEIGHT - icon_size*3;
|
|
startx += (icon_size+2);
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
}
|
|
|
|
static qboolean CG_IsDurationPower(int power)
|
|
{
|
|
if (power == FP_HEAL ||
|
|
power == FP_SPEED ||
|
|
power == FP_TELEPATHY ||
|
|
power == FP_RAGE ||
|
|
power == FP_PROTECT ||
|
|
power == FP_ABSORB ||
|
|
power == FP_SEE)
|
|
{
|
|
return qtrue;
|
|
}
|
|
|
|
return qfalse;
|
|
}
|
|
|
|
//--------------------------------------------------------------
|
|
static void CG_DrawActivePowers(void)
|
|
//--------------------------------------------------------------
|
|
{
|
|
int icon_size = 40;
|
|
int i = 0;
|
|
int startx = icon_size*2+16;
|
|
int starty = SCREEN_HEIGHT - icon_size*2;
|
|
|
|
int endx = icon_size;
|
|
int endy = icon_size;
|
|
|
|
if (cg.snap->ps.zoomMode)
|
|
{ //don't display over zoom mask
|
|
return;
|
|
}
|
|
|
|
if (cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR)
|
|
{
|
|
return;
|
|
}
|
|
|
|
while (i < NUM_FORCE_POWERS)
|
|
{
|
|
if ((cg.snap->ps.fd.forcePowersActive & (1 << forcePowerSorted[i])) &&
|
|
CG_IsDurationPower(forcePowerSorted[i]))
|
|
{
|
|
CG_DrawPic( startx, starty, endx, endy, cgs.media.forcePowerIcons[forcePowerSorted[i]]);
|
|
startx += (icon_size+2); //+2 for spacing
|
|
if ((startx+icon_size) >= SCREEN_WIDTH-80)
|
|
{
|
|
startx = icon_size*2+16;
|
|
starty += (icon_size+2);
|
|
}
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
//additionally, draw an icon force force rage recovery
|
|
if (cg.snap->ps.fd.forceRageRecoveryTime > cg.time)
|
|
{
|
|
CG_DrawPic( startx, starty, endx, endy, cgs.media.rageRecShader);
|
|
}
|
|
}
|
|
|
|
//static qboolean cg_drawingRocketLockThisFrame = qfalse;
|
|
//--------------------------------------------------------------
|
|
static void CG_DrawRocketLocking( int lockEntNum, int lockTime )
|
|
//--------------------------------------------------------------
|
|
{
|
|
int cx, cy;
|
|
vec3_t org;
|
|
static int oldDif = 0;
|
|
centity_t *cent = &cg_entities[lockEntNum];
|
|
vec4_t color={0.0f,0.0f,0.0f,0.0f};
|
|
float lockTimeInterval = ((cgs.gametype==GT_SIEGE)?2400.0f:1200.0f)/16.0f;
|
|
//FIXME: if in a vehicle, use the vehicle's lockOnTime...
|
|
int dif = (cg.time - cg.snap->ps.rocketLockTime)/lockTimeInterval;
|
|
int i;
|
|
|
|
// cg_drawingRocketLockThisFrame = qfalse;
|
|
|
|
if (!cg.snap->ps.rocketLockTime)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.snap->ps.m_iVehicleNum )
|
|
{//driving a vehicle
|
|
centity_t *veh = &cg_entities[cg.snap->ps.m_iVehicleNum];
|
|
if ( veh->m_pVehicle )
|
|
{
|
|
vehWeaponInfo_t *vehWeapon = NULL;
|
|
if ( cg.predictedVehicleState.weaponstate == WEAPON_CHARGING_ALT )
|
|
{
|
|
if ( veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID > VEH_WEAPON_BASE
|
|
&& veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID < MAX_VEH_WEAPONS )
|
|
{
|
|
vehWeapon = &g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[1].ID];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID > VEH_WEAPON_BASE
|
|
&& veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID < MAX_VEH_WEAPONS )
|
|
{
|
|
vehWeapon = &g_vehWeaponInfo[veh->m_pVehicle->m_pVehicleInfo->weapon[0].ID];
|
|
}
|
|
}
|
|
if ( vehWeapon != NULL )
|
|
{//we are trying to lock on with a valid vehicle weapon, so use *its* locktime, not the hard-coded one
|
|
if ( !vehWeapon->iLockOnTime )
|
|
{//instant lock-on
|
|
dif = 10.0f;
|
|
}
|
|
else
|
|
{//use the custom vehicle lockOnTime
|
|
lockTimeInterval = (vehWeapon->iLockOnTime/16.0f);
|
|
dif = (cg.time - cg.snap->ps.rocketLockTime)/lockTimeInterval;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//We can't check to see in pmove if players are on the same team, so we resort
|
|
//to just not drawing the lock if a teammate is the locked on ent
|
|
if (cg.snap->ps.rocketLockIndex >= 0 &&
|
|
cg.snap->ps.rocketLockIndex < ENTITYNUM_NONE)
|
|
{
|
|
clientInfo_t *ci = NULL;
|
|
|
|
if (cg.snap->ps.rocketLockIndex < MAX_CLIENTS)
|
|
{
|
|
ci = &cgs.clientinfo[cg.snap->ps.rocketLockIndex];
|
|
}
|
|
else
|
|
{
|
|
ci = cg_entities[cg.snap->ps.rocketLockIndex].npcClient;
|
|
}
|
|
|
|
if (ci)
|
|
{
|
|
if (ci->team == cgs.clientinfo[cg.snap->ps.clientNum].team)
|
|
{
|
|
if (cgs.gametype >= GT_TEAM)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
else if (cgs.gametype >= GT_TEAM)
|
|
{
|
|
centity_t *hitEnt = &cg_entities[cg.snap->ps.rocketLockIndex];
|
|
if (hitEnt->currentState.eType == ET_NPC &&
|
|
hitEnt->currentState.NPC_class == CLASS_VEHICLE &&
|
|
hitEnt->currentState.owner < ENTITYNUM_WORLD)
|
|
{ //this is a vehicle, if it has a pilot and that pilot is on my team, then...
|
|
if (hitEnt->currentState.owner < MAX_CLIENTS)
|
|
{
|
|
ci = &cgs.clientinfo[hitEnt->currentState.owner];
|
|
}
|
|
else
|
|
{
|
|
ci = cg_entities[hitEnt->currentState.owner].npcClient;
|
|
}
|
|
if (ci && ci->team == cgs.clientinfo[cg.snap->ps.clientNum].team)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (cg.snap->ps.rocketLockTime != -1)
|
|
{
|
|
lastvalidlockdif = dif;
|
|
}
|
|
else
|
|
{
|
|
dif = lastvalidlockdif;
|
|
}
|
|
|
|
if ( !cent )
|
|
{
|
|
return;
|
|
}
|
|
|
|
VectorCopy( cent->lerpOrigin, org );
|
|
|
|
if ( CG_WorldCoordToScreenCoord( org, &cx, &cy ))
|
|
{
|
|
// we care about distance from enemy to eye, so this is good enough
|
|
float sz = Distance( cent->lerpOrigin, cg.refdef.vieworg ) / 1024.0f;
|
|
|
|
if ( sz > 1.0f )
|
|
{
|
|
sz = 1.0f;
|
|
}
|
|
else if ( sz < 0.0f )
|
|
{
|
|
sz = 0.0f;
|
|
}
|
|
|
|
sz = (1.0f - sz) * (1.0f - sz) * 32 + 6;
|
|
|
|
if ( cg.snap->ps.m_iVehicleNum )
|
|
{
|
|
sz *= 2.0f;
|
|
}
|
|
|
|
cy += sz * 0.5f;
|
|
|
|
if ( dif < 0 )
|
|
{
|
|
oldDif = 0;
|
|
return;
|
|
}
|
|
else if ( dif > 8 )
|
|
{
|
|
dif = 8;
|
|
}
|
|
|
|
// do sounds
|
|
if ( oldDif != dif )
|
|
{
|
|
if ( dif == 8 )
|
|
{
|
|
if ( cg.snap->ps.m_iVehicleNum )
|
|
{
|
|
trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/vehicles/weapons/common/lock.wav" ));
|
|
}
|
|
else
|
|
{
|
|
trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/rocket/lock.wav" ));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( cg.snap->ps.m_iVehicleNum )
|
|
{
|
|
trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/vehicles/weapons/common/tick.wav" ));
|
|
}
|
|
else
|
|
{
|
|
trap_S_StartSound( org, 0, CHAN_AUTO, trap_S_RegisterSound( "sound/weapons/rocket/tick.wav" ));
|
|
}
|
|
}
|
|
}
|
|
|
|
oldDif = dif;
|
|
|
|
for ( i = 0; i < dif; i++ )
|
|
{
|
|
color[0] = 1.0f;
|
|
color[1] = 0.0f;
|
|
color[2] = 0.0f;
|
|
color[3] = 0.1f * i + 0.2f;
|
|
|
|
trap_R_SetColor( color );
|
|
|
|
// our slices are offset by about 45 degrees.
|
|
CG_DrawRotatePic( cx - sz, cy - sz, sz, sz, i * 45.0f, trap_R_RegisterShaderNoMip( "gfx/2d/wedge" ));
|
|
}
|
|
|
|
// we are locked and loaded baby
|
|
if ( dif == 8 )
|
|
{
|
|
color[0] = color[1] = color[2] = sin( cg.time * 0.05f ) * 0.5f + 0.5f;
|
|
color[3] = 1.0f; // this art is additive, so the alpha value does nothing
|
|
|
|
trap_R_SetColor( color );
|
|
|
|
CG_DrawPic( cx - sz, cy - sz * 2, sz * 2, sz * 2, trap_R_RegisterShaderNoMip( "gfx/2d/lock" ));
|
|
}
|
|
// cg_drawingRocketLockThisFrame = qtrue;
|
|
}
|
|
}
|
|
|
|
extern void CG_CalcVehMuzzle(Vehicle_t *pVeh, centity_t *ent, int muzzleNum);
|
|
qboolean CG_CalcVehicleMuzzlePoint( int entityNum, vec3_t start, vec3_t d_f, vec3_t d_rt, vec3_t d_up)
|
|
{
|
|
centity_t *vehCent = &cg_entities[entityNum];
|
|
if ( vehCent->m_pVehicle && vehCent->m_pVehicle->m_pVehicleInfo->type == VH_WALKER )
|
|
{//draw from barrels
|
|
VectorCopy( vehCent->lerpOrigin, start );
|
|
start[2] += vehCent->m_pVehicle->m_pVehicleInfo->height-DEFAULT_MINS_2-48;
|
|
AngleVectors( vehCent->lerpAngles, d_f, d_rt, d_up );
|
|
/*
|
|
mdxaBone_t boltMatrix;
|
|
int bolt;
|
|
vec3_t yawOnlyAngles;
|
|
|
|
VectorSet( yawOnlyAngles, 0, vehCent->lerpAngles[YAW], 0 );
|
|
|
|
bolt = trap_G2API_AddBolt( vehCent->ghoul2, 0, "*flash1");
|
|
trap_G2API_GetBoltMatrix( vehCent->ghoul2, 0, bolt, &boltMatrix,
|
|
yawOnlyAngles, vehCent->lerpOrigin, cg.time,
|
|
NULL, vehCent->modelScale );
|
|
|
|
// work the matrix axis stuff into the original axis and origins used.
|
|
BG_GiveMeVectorFromMatrix( &boltMatrix, ORIGIN, start );
|
|
BG_GiveMeVectorFromMatrix( &boltMatrix, POSITIVE_X, d_f );
|
|
VectorClear( d_rt );//don't really need this, do we?
|
|
VectorClear( d_up );//don't really need this, do we?
|
|
*/
|
|
}
|
|
else
|
|
{
|
|
//check to see if we're a turret gunner on this vehicle
|
|
if ( cg.predictedPlayerState.generic1 )//as a passenger
|
|
{//passenger in a vehicle
|
|
if ( vehCent->m_pVehicle
|
|
&& vehCent->m_pVehicle->m_pVehicleInfo
|
|
&& vehCent->m_pVehicle->m_pVehicleInfo->maxPassengers )
|
|
{//a vehicle capable of carrying passengers
|
|
int turretNum;
|
|
for ( turretNum = 0; turretNum < MAX_VEHICLE_TURRETS; turretNum++ )
|
|
{
|
|
if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].iAmmoMax )
|
|
{// valid turret
|
|
if ( vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].passengerNum == cg.predictedPlayerState.generic1 )
|
|
{//I control this turret
|
|
//Go through all muzzles, average their positions and directions and use the result for crosshair trace
|
|
int vehMuzzle, numMuzzles = 0;
|
|
vec3_t muzzlesAvgPos={0},muzzlesAvgDir={0};
|
|
int i;
|
|
|
|
for ( i = 0; i < MAX_VEHICLE_TURRET_MUZZLES; i++ )
|
|
{
|
|
vehMuzzle = vehCent->m_pVehicle->m_pVehicleInfo->turret[turretNum].iMuzzle[i];
|
|
if ( vehMuzzle )
|
|
{
|
|
vehMuzzle -= 1;
|
|
CG_CalcVehMuzzle( vehCent->m_pVehicle, vehCent, vehMuzzle );
|
|
VectorAdd( muzzlesAvgPos, vehCent->m_pVehicle->m_vMuzzlePos[vehMuzzle], muzzlesAvgPos );
|
|
VectorAdd( muzzlesAvgDir, vehCent->m_pVehicle->m_vMuzzleDir[vehMuzzle], muzzlesAvgDir );
|
|
numMuzzles++;
|
|
}
|
|
if ( numMuzzles )
|
|
{
|
|
VectorScale( muzzlesAvgPos, 1.0f/(float)numMuzzles, start );
|
|
VectorScale( muzzlesAvgDir, 1.0f/(float)numMuzzles, d_f );
|
|
VectorClear( d_rt );
|
|
VectorClear( d_up );
|
|
return qtrue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
VectorCopy( vehCent->lerpOrigin, start );
|
|
AngleVectors( vehCent->lerpAngles, d_f, d_rt, d_up );
|
|
}
|
|
return qfalse;
|
|
}
|
|
|
|
//calc the muzzle point from the e-web itself
|
|
void CG_CalcEWebMuzzlePoint(centity_t *cent, vec3_t start, vec3_t d_f, vec3_t d_rt, vec3_t d_up)
|
|
{
|
|
int bolt = trap_G2API_AddBolt(cent->ghoul2, 0, "*cannonflash");
|
|
|
|
assert(bolt != -1);
|
|
|
|
if (bolt != -1)
|
|
{
|
|
mdxaBone_t boltMatrix;
|
|
|
|
trap_G2API_GetBoltMatrix_NoRecNoRot(cent->ghoul2, 0, bolt, &boltMatrix, cent->lerpAngles, cent->lerpOrigin, cg.time, NULL, cent->modelScale);
|
|
BG_GiveMeVectorFromMatrix(&boltMatrix, ORIGIN, start);
|
|
BG_GiveMeVectorFromMatrix(&boltMatrix, NEGATIVE_X, d_f);
|
|
|
|
//these things start the shot a little inside the bbox to assure not starting in something solid
|
|
VectorMA(start, -16.0f, d_f, start);
|
|
|
|
//I guess
|
|
VectorClear( d_rt );//don't really need this, do we?
|
|
VectorClear( d_up );//don't really need this, do we?
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_`Entity
|
|
=================
|
|
*/
|
|
#define MAX_XHAIR_DIST_ACCURACY 20000.0f
|
|
static void CG_ScanForCrosshairEntity( void ) {
|
|
trace_t trace;
|
|
vec3_t start, end;
|
|
int content;
|
|
int ignore;
|
|
qboolean bVehCheckTraceFromCamPos = qfalse;
|
|
|
|
ignore = cg.predictedPlayerState.clientNum;
|
|
|
|
if ( cg_dynamicCrosshair.integer )
|
|
{
|
|
vec3_t d_f, d_rt, d_up;
|
|
/*
|
|
if ( cg.snap->ps.weapon == WP_NONE ||
|
|
cg.snap->ps.weapon == WP_SABER ||
|
|
cg.snap->ps.weapon == WP_STUN_BATON)
|
|
{
|
|
VectorCopy( cg.refdef.vieworg, start );
|
|
AngleVectors( cg.refdef.viewangles, d_f, d_rt, d_up );
|
|
}
|
|
else
|
|
*/
|
|
//For now we still want to draw the crosshair in relation to the player's world coordinates
|
|
//even if we have a melee weapon/no weapon.
|
|
if ( cg.predictedPlayerState.m_iVehicleNum && (cg.predictedPlayerState.eFlags&EF_NODRAW) )
|
|
{//we're *inside* a vehicle
|
|
//do the vehicle's crosshair instead
|
|
centity_t *veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum];
|
|
qboolean gunner = qfalse;
|
|
|
|
//if (veh->currentState.owner == cg.predictedPlayerState.clientNum)
|
|
{ //the pilot
|
|
ignore = cg.predictedPlayerState.m_iVehicleNum;
|
|
gunner = CG_CalcVehicleMuzzlePoint(cg.predictedPlayerState.m_iVehicleNum, start, d_f, d_rt, d_up);
|
|
}
|
|
/*
|
|
else
|
|
{ //a passenger
|
|
ignore = cg.predictedPlayerState.m_iVehicleNum;
|
|
VectorCopy( veh->lerpOrigin, start );
|
|
AngleVectors( veh->lerpAngles, d_f, d_rt, d_up );
|
|
VectorMA(start, 32.0f, d_f, start); //super hack
|
|
}
|
|
*/
|
|
if ( veh->m_pVehicle
|
|
&& veh->m_pVehicle->m_pVehicleInfo
|
|
&& veh->m_pVehicle->m_pVehicleInfo->type == VH_FIGHTER
|
|
&& cg.distanceCull > MAX_XHAIR_DIST_ACCURACY
|
|
&& !gunner )
|
|
{
|
|
//NOTE: on huge maps, the crosshair gets inaccurate at close range,
|
|
// so we'll do an extra G2 trace from the cg.refdef.vieworg
|
|
// to see if we hit anything closer and auto-aim at it if so
|
|
bVehCheckTraceFromCamPos = qtrue;
|
|
}
|
|
}
|
|
else if (cg.snap && cg.snap->ps.weapon == WP_EMPLACED_GUN && cg.snap->ps.emplacedIndex &&
|
|
cg_entities[cg.snap->ps.emplacedIndex].ghoul2 && cg_entities[cg.snap->ps.emplacedIndex].currentState.weapon == WP_NONE)
|
|
{ //locked into our e-web, calc the muzzle from it
|
|
CG_CalcEWebMuzzlePoint(&cg_entities[cg.snap->ps.emplacedIndex], start, d_f, d_rt, d_up);
|
|
}
|
|
else
|
|
{
|
|
if (cg.snap && cg.snap->ps.weapon == WP_EMPLACED_GUN && cg.snap->ps.emplacedIndex)
|
|
{
|
|
vec3_t pitchConstraint;
|
|
|
|
ignore = cg.snap->ps.emplacedIndex;
|
|
|
|
VectorCopy(cg.refdef.viewangles, pitchConstraint);
|
|
|
|
if (cg.renderingThirdPerson)
|
|
{
|
|
VectorCopy(cg.predictedPlayerState.viewangles, pitchConstraint);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(cg.refdef.viewangles, pitchConstraint);
|
|
}
|
|
|
|
if (pitchConstraint[PITCH] > 40)
|
|
{
|
|
pitchConstraint[PITCH] = 40;
|
|
}
|
|
|
|
AngleVectors( pitchConstraint, d_f, d_rt, d_up );
|
|
}
|
|
else
|
|
{
|
|
vec3_t pitchConstraint;
|
|
|
|
if (cg.renderingThirdPerson)
|
|
{
|
|
VectorCopy(cg.predictedPlayerState.viewangles, pitchConstraint);
|
|
}
|
|
else
|
|
{
|
|
VectorCopy(cg.refdef.viewangles, pitchConstraint);
|
|
}
|
|
|
|
AngleVectors( pitchConstraint, d_f, d_rt, d_up );
|
|
}
|
|
CG_CalcMuzzlePoint(cg.snap->ps.clientNum, start);
|
|
}
|
|
|
|
VectorMA( start, cg.distanceCull, d_f, end );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy( cg.refdef.vieworg, start );
|
|
VectorMA( start, 131072, cg.refdef.viewaxis[0], end );
|
|
}
|
|
|
|
if ( cg_dynamicCrosshair.integer && cg_dynamicCrosshairPrecision.integer )
|
|
{ //then do a trace with ghoul2 models in mind
|
|
CG_G2Trace( &trace, start, vec3_origin, vec3_origin, end,
|
|
ignore, CONTENTS_SOLID|CONTENTS_BODY );
|
|
if ( bVehCheckTraceFromCamPos )
|
|
{
|
|
//NOTE: this MUST stay up to date with the method used in WP_VehCheckTraceFromCamPos
|
|
centity_t *veh = &cg_entities[cg.predictedPlayerState.m_iVehicleNum];
|
|
trace_t extraTrace;
|
|
vec3_t viewDir2End, extraEnd;
|
|
float minAutoAimDist = Distance( veh->lerpOrigin, cg.refdef.vieworg ) + (veh->m_pVehicle->m_pVehicleInfo->length/2.0f) + 200.0f;
|
|
|
|
VectorSubtract( end, cg.refdef.vieworg, viewDir2End );
|
|
VectorNormalize( viewDir2End );
|
|
VectorMA( cg.refdef.vieworg, MAX_XHAIR_DIST_ACCURACY, viewDir2End, extraEnd );
|
|
CG_G2Trace( &extraTrace, cg.refdef.vieworg, vec3_origin, vec3_origin, extraEnd,
|
|
ignore, CONTENTS_SOLID|CONTENTS_BODY );
|
|
if ( !extraTrace.allsolid
|
|
&& !extraTrace.startsolid )
|
|
{
|
|
if ( extraTrace.fraction < 1.0f )
|
|
{
|
|
if ( (extraTrace.fraction*MAX_XHAIR_DIST_ACCURACY) > minAutoAimDist )
|
|
{
|
|
if ( ((extraTrace.fraction*MAX_XHAIR_DIST_ACCURACY)-Distance( veh->lerpOrigin, cg.refdef.vieworg )) < (trace.fraction*cg.distanceCull) )
|
|
{//this trace hit *something* that's closer than the thing the main trace hit, so use this result instead
|
|
memcpy( &trace, &extraTrace, sizeof( trace_t ) );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CG_Trace( &trace, start, vec3_origin, vec3_origin, end,
|
|
ignore, CONTENTS_SOLID|CONTENTS_BODY );
|
|
}
|
|
|
|
if (trace.entityNum < MAX_CLIENTS)
|
|
{
|
|
if (CG_IsMindTricked(cg_entities[trace.entityNum].currentState.trickedentindex,
|
|
cg_entities[trace.entityNum].currentState.trickedentindex2,
|
|
cg_entities[trace.entityNum].currentState.trickedentindex3,
|
|
cg_entities[trace.entityNum].currentState.trickedentindex4,
|
|
cg.snap->ps.clientNum))
|
|
{
|
|
if (cg.crosshairClientNum == trace.entityNum)
|
|
{
|
|
cg.crosshairClientNum = ENTITYNUM_NONE;
|
|
cg.crosshairClientTime = 0;
|
|
}
|
|
|
|
CG_DrawCrosshair(trace.endpos, 0);
|
|
|
|
return; //this entity is mind-tricking the current client, so don't render it
|
|
}
|
|
}
|
|
|
|
if (cg.snap->ps.persistant[PERS_TEAM] != TEAM_SPECTATOR)
|
|
{
|
|
if (trace.entityNum < /*MAX_CLIENTS*/ENTITYNUM_WORLD)
|
|
{
|
|
cg.crosshairClientNum = trace.entityNum;
|
|
cg.crosshairClientTime = cg.time;
|
|
|
|
if (cg.crosshairClientNum < ENTITYNUM_WORLD)
|
|
{
|
|
centity_t *veh = &cg_entities[cg.crosshairClientNum];
|
|
|
|
if (veh->currentState.eType == ET_NPC &&
|
|
veh->currentState.NPC_class == CLASS_VEHICLE &&
|
|
veh->currentState.owner < MAX_CLIENTS)
|
|
{ //draw the name of the pilot then
|
|
cg.crosshairClientNum = veh->currentState.owner;
|
|
cg.crosshairVehNum = veh->currentState.number;
|
|
cg.crosshairVehTime = cg.time;
|
|
}
|
|
}
|
|
|
|
CG_DrawCrosshair(trace.endpos, 1);
|
|
}
|
|
else
|
|
{
|
|
CG_DrawCrosshair(trace.endpos, 0);
|
|
}
|
|
}
|
|
|
|
// if ( trace.entityNum >= MAX_CLIENTS ) {
|
|
// return;
|
|
// }
|
|
|
|
// if the player is in fog, don't show it
|
|
content = trap_CM_PointContents( trace.endpos, 0 );
|
|
if ( content & CONTENTS_FOG ) {
|
|
return;
|
|
}
|
|
|
|
// update the fade timer
|
|
cg.crosshairClientNum = trace.entityNum;
|
|
cg.crosshairClientTime = cg.time;
|
|
}
|
|
|
|
void CG_SanitizeString( char *in, char *out )
|
|
{
|
|
int i = 0;
|
|
int r = 0;
|
|
|
|
while (in[i])
|
|
{
|
|
if (i >= 128-1)
|
|
{ //the ui truncates the name here..
|
|
break;
|
|
}
|
|
|
|
if (in[i] == '^')
|
|
{
|
|
if (in[i+1] >= 48 && //'0'
|
|
in[i+1] <= 57) //'9'
|
|
{ //only skip it if there's a number after it for the color
|
|
i += 2;
|
|
continue;
|
|
}
|
|
else
|
|
{ //just skip the ^
|
|
i++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (in[i] < 32)
|
|
{
|
|
i++;
|
|
continue;
|
|
}
|
|
|
|
out[r] = in[i];
|
|
r++;
|
|
i++;
|
|
}
|
|
out[r] = 0;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawCrosshairNames
|
|
=====================
|
|
*/
|
|
static void CG_DrawCrosshairNames( void ) {
|
|
float *color;
|
|
vec4_t tcolor;
|
|
char *name;
|
|
char sanitized[1024];
|
|
int baseColor;
|
|
qboolean isVeh = qfalse;
|
|
|
|
if ( !cg_drawCrosshair.integer ) {
|
|
return;
|
|
}
|
|
|
|
// scan the known entities to see if the crosshair is sighted on one
|
|
CG_ScanForCrosshairEntity();
|
|
|
|
if ( !cg_drawCrosshairNames.integer ) {
|
|
return;
|
|
}
|
|
//rww - still do the trace, our dynamic crosshair depends on it
|
|
|
|
if (cg.crosshairClientNum < ENTITYNUM_WORLD)
|
|
{
|
|
centity_t *veh = &cg_entities[cg.crosshairClientNum];
|
|
|
|
if (veh->currentState.eType == ET_NPC &&
|
|
veh->currentState.NPC_class == CLASS_VEHICLE &&
|
|
veh->currentState.owner < MAX_CLIENTS)
|
|
{ //draw the name of the pilot then
|
|
cg.crosshairClientNum = veh->currentState.owner;
|
|
cg.crosshairVehNum = veh->currentState.number;
|
|
cg.crosshairVehTime = cg.time;
|
|
isVeh = qtrue; //so we know we're drawing the pilot's name
|
|
}
|
|
}
|
|
|
|
if (cg.crosshairClientNum >= MAX_CLIENTS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cg_entities[cg.crosshairClientNum].currentState.powerups & (1 << PW_CLOAKED))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// draw the name of the player being looked at
|
|
color = CG_FadeColor( cg.crosshairClientTime, 1000 );
|
|
if ( !color ) {
|
|
trap_R_SetColor( NULL );
|
|
return;
|
|
}
|
|
|
|
name = cgs.clientinfo[ cg.crosshairClientNum ].name;
|
|
|
|
if (cgs.gametype >= GT_TEAM)
|
|
{
|
|
//if (cgs.gametype == GT_SIEGE)
|
|
if (1)
|
|
{ //instead of team-based we'll make it oriented based on which team we're on
|
|
if (cgs.clientinfo[cg.crosshairClientNum].team == cg.predictedPlayerState.persistant[PERS_TEAM])
|
|
{
|
|
baseColor = CT_GREEN;
|
|
}
|
|
else
|
|
{
|
|
baseColor = CT_RED;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (cgs.clientinfo[cg.crosshairClientNum].team == TEAM_RED)
|
|
{
|
|
baseColor = CT_RED;
|
|
}
|
|
else
|
|
{
|
|
baseColor = CT_BLUE;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//baseColor = CT_WHITE;
|
|
if (cgs.gametype == GT_POWERDUEL &&
|
|
cgs.clientinfo[cg.snap->ps.clientNum].team != TEAM_SPECTATOR &&
|
|
cgs.clientinfo[cg.crosshairClientNum].duelTeam == cgs.clientinfo[cg.predictedPlayerState.clientNum].duelTeam)
|
|
{ //on the same duel team in powerduel, so he's a friend
|
|
baseColor = CT_GREEN;
|
|
}
|
|
else
|
|
{
|
|
baseColor = CT_RED; //just make it red in nonteam modes since everyone is hostile and crosshair will be red on them too
|
|
}
|
|
}
|
|
|
|
if (cg.snap->ps.duelInProgress)
|
|
{
|
|
if (cg.crosshairClientNum != cg.snap->ps.duelIndex)
|
|
{ //grey out crosshair for everyone but your foe if you're in a duel
|
|
baseColor = CT_BLACK;
|
|
}
|
|
}
|
|
else if (cg_entities[cg.crosshairClientNum].currentState.bolt1)
|
|
{ //this fellow is in a duel. We just checked if we were in a duel above, so
|
|
//this means we aren't and he is. Which of course means our crosshair greys out over him.
|
|
baseColor = CT_BLACK;
|
|
}
|
|
|
|
tcolor[0] = colorTable[baseColor][0];
|
|
tcolor[1] = colorTable[baseColor][1];
|
|
tcolor[2] = colorTable[baseColor][2];
|
|
tcolor[3] = color[3]*0.5f;
|
|
|
|
CG_SanitizeString(name, sanitized);
|
|
|
|
if (isVeh)
|
|
{
|
|
char str[MAX_STRING_CHARS];
|
|
Com_sprintf(str, MAX_STRING_CHARS, "%s (pilot)", sanitized);
|
|
UI_DrawProportionalString(320, 170, str, UI_CENTER, tcolor);
|
|
}
|
|
else
|
|
{
|
|
UI_DrawProportionalString(320, 170, sanitized, UI_CENTER, tcolor);
|
|
}
|
|
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
|
|
/*
|
|
=================
|
|
CG_DrawSpectator
|
|
=================
|
|
*/
|
|
static void CG_DrawSpectator(void)
|
|
{
|
|
const char* s;
|
|
|
|
s = CG_GetStringEdString("MP_INGAME", "SPECTATOR");
|
|
if ((cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL) &&
|
|
cgs.duelist1 != -1 &&
|
|
cgs.duelist2 != -1)
|
|
{
|
|
char text[1024];
|
|
int size = 64;
|
|
|
|
if (cgs.gametype == GT_POWERDUEL && cgs.duelist3 != -1)
|
|
{
|
|
Com_sprintf(text, sizeof(text), "%s^7 %s %s^7 %s %s", cgs.clientinfo[cgs.duelist1].name, CG_GetStringEdString("MP_INGAME", "SPECHUD_VERSUS"), cgs.clientinfo[cgs.duelist2].name, CG_GetStringEdString("MP_INGAME", "AND"), cgs.clientinfo[cgs.duelist3].name);
|
|
}
|
|
else
|
|
{
|
|
Com_sprintf(text, sizeof(text), "%s^7 %s %s", cgs.clientinfo[cgs.duelist1].name, CG_GetStringEdString("MP_INGAME", "SPECHUD_VERSUS"), cgs.clientinfo[cgs.duelist2].name);
|
|
}
|
|
CG_Text_Paint ( 320 - CG_Text_Width ( text, 1.0f, 3 ) / 2, 420, 1.0f, colorWhite, text, 0, 0, 0, 3 );
|
|
|
|
trap_R_SetColor( colorTable[CT_WHITE] );
|
|
if ( cgs.clientinfo[cgs.duelist1].modelIcon )
|
|
{
|
|
CG_DrawPic( 10, SCREEN_HEIGHT-(size*1.5), size, size, cgs.clientinfo[cgs.duelist1].modelIcon );
|
|
}
|
|
if ( cgs.clientinfo[cgs.duelist2].modelIcon )
|
|
{
|
|
CG_DrawPic( SCREEN_WIDTH-size-10, SCREEN_HEIGHT-(size*1.5), size, size, cgs.clientinfo[cgs.duelist2].modelIcon );
|
|
}
|
|
|
|
// nmckenzie: DUEL_HEALTH
|
|
if (cgs.gametype == GT_DUEL)
|
|
{
|
|
if ( cgs.showDuelHealths >= 1)
|
|
{ // draw the healths on the two guys - how does this interact with power duel, though?
|
|
CG_DrawDuelistHealth ( 10, SCREEN_HEIGHT-(size*1.5) - 12, 64, 8, 1 );
|
|
CG_DrawDuelistHealth ( SCREEN_WIDTH-size-10, SCREEN_HEIGHT-(size*1.5) - 12, 64, 8, 2 );
|
|
}
|
|
}
|
|
|
|
if (cgs.gametype != GT_POWERDUEL)
|
|
{
|
|
Com_sprintf(text, sizeof(text), "%i/%i", cgs.clientinfo[cgs.duelist1].score, cgs.fraglimit );
|
|
CG_Text_Paint( 42 - CG_Text_Width( text, 1.0f, 2 ) / 2, SCREEN_HEIGHT-(size*1.5) + 64, 1.0f, colorWhite, text, 0, 0, 0, 2 );
|
|
|
|
Com_sprintf(text, sizeof(text), "%i/%i", cgs.clientinfo[cgs.duelist2].score, cgs.fraglimit );
|
|
CG_Text_Paint( SCREEN_WIDTH-size+22 - CG_Text_Width( text, 1.0f, 2 ) / 2, SCREEN_HEIGHT-(size*1.5) + 64, 1.0f, colorWhite, text, 0, 0, 0, 2 );
|
|
}
|
|
|
|
if (cgs.gametype == GT_POWERDUEL && cgs.duelist3 != -1)
|
|
{
|
|
if ( cgs.clientinfo[cgs.duelist3].modelIcon )
|
|
{
|
|
CG_DrawPic( SCREEN_WIDTH-size-10, SCREEN_HEIGHT-(size*2.8), size, size, cgs.clientinfo[cgs.duelist3].modelIcon );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 420, 1.0f, colorWhite, s, 0, 0, 0, 3 );
|
|
}
|
|
|
|
if ( cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL )
|
|
{
|
|
s = CG_GetStringEdString("MP_INGAME", "WAITING_TO_PLAY"); // "waiting to play";
|
|
CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 440, 1.0f, colorWhite, s, 0, 0, 0, 3 );
|
|
}
|
|
else //if ( cgs.gametype >= GT_TEAM )
|
|
{
|
|
//s = "press ESC and use the JOIN menu to play";
|
|
s = CG_GetStringEdString("MP_INGAME", "SPEC_CHOOSEJOIN");
|
|
CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, 3 ) / 2, 440, 1.0f, colorWhite, s, 0, 0, 0, 3 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawVote
|
|
=================
|
|
*/
|
|
static void CG_DrawVote(void) {
|
|
const char *s;
|
|
int sec;
|
|
char sYes[20];
|
|
char sNo[20];
|
|
char sVote[20];
|
|
char sCmd[100];
|
|
const char* sParm = 0;
|
|
|
|
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;
|
|
}
|
|
|
|
if (strncmp(cgs.voteString, "map_restart", 11)==0)
|
|
{
|
|
trap_SP_GetStringTextString("MENUS_RESTART_MAP", sCmd, sizeof(sCmd) );
|
|
}
|
|
else if (strncmp(cgs.voteString, "vstr nextmap", 12)==0)
|
|
{
|
|
trap_SP_GetStringTextString("MENUS_NEXT_MAP", sCmd, sizeof(sCmd) );
|
|
}
|
|
else if (strncmp(cgs.voteString, "g_doWarmup", 10)==0)
|
|
{
|
|
trap_SP_GetStringTextString("MENUS_WARMUP", sCmd, sizeof(sCmd) );
|
|
}
|
|
else if (strncmp(cgs.voteString, "g_gametype", 10)==0)
|
|
{
|
|
trap_SP_GetStringTextString("MENUS_GAME_TYPE", sCmd, sizeof(sCmd) );
|
|
if ( Q_stricmp("Free For All", cgs.voteString+11)==0 )
|
|
{
|
|
sParm = CG_GetStringEdString("MENUS", "FREE_FOR_ALL");
|
|
}
|
|
else if ( Q_stricmp("Duel", cgs.voteString+11)==0 )
|
|
{
|
|
sParm = CG_GetStringEdString("MENUS", "DUEL");
|
|
}
|
|
else if ( Q_stricmp("Holocron FFA", cgs.voteString+11)==0 )
|
|
{
|
|
sParm = CG_GetStringEdString("MENUS", "HOLOCRON_FFA");
|
|
}
|
|
else if ( Q_stricmp("Power Duel", cgs.voteString+11)==0 )
|
|
{
|
|
sParm = CG_GetStringEdString("MENUS", "POWERDUEL");
|
|
}
|
|
else if ( Q_stricmp("Team FFA", cgs.voteString+11)==0 )
|
|
{
|
|
sParm = CG_GetStringEdString("MENUS", "TEAM_FFA");
|
|
}
|
|
else if ( Q_stricmp("Siege", cgs.voteString+11)==0 )
|
|
{
|
|
sParm = CG_GetStringEdString("MENUS", "SIEGE");
|
|
}
|
|
else if ( Q_stricmp("Capture the Flag", cgs.voteString+11)==0 )
|
|
{
|
|
sParm = CG_GetStringEdString("MENUS", "CAPTURE_THE_FLAG");
|
|
}
|
|
else if ( Q_stricmp("Capture the Ysalamiri", cgs.voteString+11)==0 )
|
|
{
|
|
sParm = CG_GetStringEdString("MENUS", "CAPTURE_THE_YSALIMARI");
|
|
}
|
|
}
|
|
else if (strncmp(cgs.voteString, "map", 3)==0)
|
|
{
|
|
trap_SP_GetStringTextString("MENUS_NEW_MAP", sCmd, sizeof(sCmd) );
|
|
sParm = cgs.voteString+4;
|
|
}
|
|
else if (strncmp(cgs.voteString, "kick", 4)==0)
|
|
{
|
|
trap_SP_GetStringTextString("MENUS_KICK_PLAYER", sCmd, sizeof(sCmd) );
|
|
sParm = cgs.voteString+5;
|
|
}
|
|
|
|
|
|
|
|
trap_SP_GetStringTextString("MENUS_VOTE", sVote, sizeof(sVote) );
|
|
trap_SP_GetStringTextString("MENUS_YES", sYes, sizeof(sYes) );
|
|
trap_SP_GetStringTextString("MENUS_NO", sNo, sizeof(sNo) );
|
|
|
|
if (sParm && sParm[0])
|
|
{
|
|
s = va("%s(%i):<%s %s> %s:%i %s:%i", sVote, sec, sCmd, sParm, sYes, cgs.voteYes, sNo, cgs.voteNo);
|
|
}
|
|
else
|
|
{
|
|
s = va("%s(%i):<%s> %s:%i %s:%i", sVote, sec, sCmd, sYes, cgs.voteYes, sNo, cgs.voteNo);
|
|
}
|
|
CG_DrawSmallString( 4, 58, s, 1.0F );
|
|
s = CG_GetStringEdString("MP_INGAME", "OR_PRESS_ESC_THEN_CLICK_VOTE"); // s = "or press ESC then click Vote";
|
|
CG_DrawSmallString( 4, 58 + SMALLCHAR_HEIGHT + 2, s, 1.0F );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawTeamVote
|
|
=================
|
|
*/
|
|
static void CG_DrawTeamVote(void) {
|
|
char *s;
|
|
int sec, cs_offset;
|
|
|
|
if ( cgs.clientinfo->team == TEAM_RED )
|
|
cs_offset = 0;
|
|
else if ( cgs.clientinfo->team == TEAM_BLUE )
|
|
cs_offset = 1;
|
|
else
|
|
return;
|
|
|
|
if ( !cgs.teamVoteTime[cs_offset] ) {
|
|
return;
|
|
}
|
|
|
|
// play a talk beep whenever it is modified
|
|
if ( cgs.teamVoteModified[cs_offset] ) {
|
|
cgs.teamVoteModified[cs_offset] = qfalse;
|
|
// trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
|
|
}
|
|
|
|
sec = ( VOTE_TIME - ( cg.time - cgs.teamVoteTime[cs_offset] ) ) / 1000;
|
|
if ( sec < 0 ) {
|
|
sec = 0;
|
|
}
|
|
if (strstr(cgs.teamVoteString[cs_offset], "leader"))
|
|
{
|
|
int i = 0;
|
|
|
|
while (cgs.teamVoteString[cs_offset][i] && cgs.teamVoteString[cs_offset][i] != ' ')
|
|
{
|
|
i++;
|
|
}
|
|
|
|
if (cgs.teamVoteString[cs_offset][i] == ' ')
|
|
{
|
|
int voteIndex = 0;
|
|
char voteIndexStr[256];
|
|
|
|
i++;
|
|
|
|
while (cgs.teamVoteString[cs_offset][i])
|
|
{
|
|
voteIndexStr[voteIndex] = cgs.teamVoteString[cs_offset][i];
|
|
voteIndex++;
|
|
i++;
|
|
}
|
|
voteIndexStr[voteIndex] = 0;
|
|
|
|
voteIndex = atoi(voteIndexStr);
|
|
|
|
s = va("TEAMVOTE(%i):(Make %s the new team leader) yes:%i no:%i", sec, cgs.clientinfo[voteIndex].name,
|
|
cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] );
|
|
}
|
|
else
|
|
{
|
|
s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset],
|
|
cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
s = va("TEAMVOTE(%i):%s yes:%i no:%i", sec, cgs.teamVoteString[cs_offset],
|
|
cgs.teamVoteYes[cs_offset], cgs.teamVoteNo[cs_offset] );
|
|
}
|
|
CG_DrawSmallString( 4, 90, s, 1.0F );
|
|
}
|
|
|
|
static qboolean CG_DrawScoreboard() {
|
|
return CG_DrawOldScoreboard();
|
|
#if 0
|
|
static qboolean firstTime = qtrue;
|
|
float fade, *fadeColor;
|
|
|
|
if (menuScoreboard) {
|
|
menuScoreboard->window.flags &= ~WINDOW_FORCED;
|
|
}
|
|
if (cg_paused.integer) {
|
|
cg.deferredPlayerLoading = 0;
|
|
firstTime = qtrue;
|
|
return qfalse;
|
|
}
|
|
|
|
// should never happen in Team Arena
|
|
if (cgs.gametype == GT_SINGLE_PLAYER && cg.predictedPlayerState.pm_type == PM_INTERMISSION ) {
|
|
cg.deferredPlayerLoading = 0;
|
|
firstTime = qtrue;
|
|
return qfalse;
|
|
}
|
|
|
|
// don't draw scoreboard during death while warmup up
|
|
if ( cg.warmup && !cg.showScores ) {
|
|
return qfalse;
|
|
}
|
|
|
|
if ( cg.showScores || cg.predictedPlayerState.pm_type == PM_DEAD || cg.predictedPlayerState.pm_type == PM_INTERMISSION ) {
|
|
fade = 1.0;
|
|
fadeColor = colorWhite;
|
|
} else {
|
|
fadeColor = CG_FadeColor( cg.scoreFadeTime, FADE_TIME );
|
|
if ( !fadeColor ) {
|
|
// next time scoreboard comes up, don't print killer
|
|
cg.deferredPlayerLoading = 0;
|
|
cg.killerName[0] = 0;
|
|
firstTime = qtrue;
|
|
return qfalse;
|
|
}
|
|
fade = *fadeColor;
|
|
}
|
|
|
|
|
|
if (menuScoreboard == NULL) {
|
|
if ( cgs.gametype >= GT_TEAM ) {
|
|
menuScoreboard = Menus_FindByName("teamscore_menu");
|
|
} else {
|
|
menuScoreboard = Menus_FindByName("score_menu");
|
|
}
|
|
}
|
|
|
|
if (menuScoreboard) {
|
|
if (firstTime) {
|
|
CG_SetScoreSelection(menuScoreboard);
|
|
firstTime = qfalse;
|
|
}
|
|
Menu_Paint(menuScoreboard, qtrue);
|
|
}
|
|
|
|
// load any models that have been deferred
|
|
if ( ++cg.deferredPlayerLoading > 10 ) {
|
|
CG_LoadDeferredPlayers();
|
|
}
|
|
|
|
return qtrue;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawIntermission
|
|
=================
|
|
*/
|
|
static void CG_DrawIntermission( void ) {
|
|
// int key;
|
|
//if (cg_singlePlayer.integer) {
|
|
// CG_DrawCenterString();
|
|
// return;
|
|
//}
|
|
cg.scoreFadeTime = cg.time;
|
|
cg.scoreBoardShowing = CG_DrawScoreboard();
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawFollow
|
|
=================
|
|
*/
|
|
static qboolean CG_DrawFollow( void )
|
|
{
|
|
const char *s;
|
|
|
|
if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
// s = "following";
|
|
if (cgs.gametype == GT_POWERDUEL)
|
|
{
|
|
clientInfo_t *ci = &cgs.clientinfo[ cg.snap->ps.clientNum ];
|
|
|
|
if (ci->duelTeam == DUELTEAM_LONE)
|
|
{
|
|
s = CG_GetStringEdString("MP_INGAME", "FOLLOWINGLONE");
|
|
}
|
|
else if (ci->duelTeam == DUELTEAM_DOUBLE)
|
|
{
|
|
s = CG_GetStringEdString("MP_INGAME", "FOLLOWINGDOUBLE");
|
|
}
|
|
else
|
|
{
|
|
s = CG_GetStringEdString("MP_INGAME", "FOLLOWING");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
s = CG_GetStringEdString("MP_INGAME", "FOLLOWING");
|
|
}
|
|
|
|
CG_Text_Paint ( 320 - CG_Text_Width ( s, 1.0f, FONT_MEDIUM ) / 2, 60, 1.0f, colorWhite, s, 0, 0, 0, FONT_MEDIUM );
|
|
|
|
s = cgs.clientinfo[ cg.snap->ps.clientNum ].name;
|
|
CG_Text_Paint ( 320 - CG_Text_Width ( s, 2.0f, FONT_MEDIUM ) / 2, 80, 2.0f, colorWhite, s, 0, 0, 0, FONT_MEDIUM );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
#if 0
|
|
static void CG_DrawTemporaryStats()
|
|
{ //placeholder for testing (draws ammo and force power)
|
|
char s[512];
|
|
|
|
if (!cg.snap)
|
|
{
|
|
return;
|
|
}
|
|
|
|
sprintf(s, "Force: %i", cg.snap->ps.fd.forcePower);
|
|
|
|
CG_DrawBigString(SCREEN_WIDTH-164, SCREEN_HEIGHT-dmgIndicSize, s, 1.0f);
|
|
|
|
sprintf(s, "Ammo: %i", cg.snap->ps.ammo[weaponData[cg.snap->ps.weapon].ammoIndex]);
|
|
|
|
CG_DrawBigString(SCREEN_WIDTH-164, SCREEN_HEIGHT-112, s, 1.0f);
|
|
|
|
sprintf(s, "Health: %i", cg.snap->ps.stats[STAT_HEALTH]);
|
|
|
|
CG_DrawBigString(8, SCREEN_HEIGHT-dmgIndicSize, s, 1.0f);
|
|
|
|
sprintf(s, "Armor: %i", cg.snap->ps.stats[STAT_ARMOR]);
|
|
|
|
CG_DrawBigString(8, SCREEN_HEIGHT-112, s, 1.0f);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
=================
|
|
CG_DrawAmmoWarning
|
|
=================
|
|
*/
|
|
static void CG_DrawAmmoWarning( void ) {
|
|
#if 0
|
|
const char *s;
|
|
int w;
|
|
|
|
if (!cg_drawStatus.integer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg_drawAmmoWarning.integer == 0 ) {
|
|
return;
|
|
}
|
|
|
|
if ( !cg.lowAmmoWarning ) {
|
|
return;
|
|
}
|
|
|
|
if ( cg.lowAmmoWarning == 2 ) {
|
|
s = "OUT OF AMMO";
|
|
} else {
|
|
s = "LOW AMMO WARNING";
|
|
}
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
CG_DrawBigString(320 - w / 2, 64, s, 1.0F);
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
=================
|
|
CG_DrawWarmup
|
|
=================
|
|
*/
|
|
static void CG_DrawWarmup( void ) {
|
|
int w;
|
|
int sec;
|
|
int i;
|
|
float scale;
|
|
int cw;
|
|
const char *s;
|
|
|
|
sec = cg.warmup;
|
|
if ( !sec ) {
|
|
return;
|
|
}
|
|
|
|
if ( sec < 0 ) {
|
|
// s = "Waiting for players";
|
|
s = CG_GetStringEdString("MP_INGAME", "WAITING_FOR_PLAYERS");
|
|
w = CG_DrawStrlen( s ) * BIGCHAR_WIDTH;
|
|
CG_DrawBigString(320 - w / 2, 24, s, 1.0F);
|
|
cg.warmupCount = 0;
|
|
return;
|
|
}
|
|
|
|
if (cgs.gametype == GT_DUEL || cgs.gametype == GT_POWERDUEL)
|
|
{
|
|
// find the two active players
|
|
clientInfo_t *ci1, *ci2, *ci3;
|
|
|
|
ci1 = NULL;
|
|
ci2 = NULL;
|
|
ci3 = NULL;
|
|
|
|
if (cgs.gametype == GT_POWERDUEL)
|
|
{
|
|
if (cgs.duelist1 != -1)
|
|
{
|
|
ci1 = &cgs.clientinfo[cgs.duelist1];
|
|
}
|
|
if (cgs.duelist2 != -1)
|
|
{
|
|
ci2 = &cgs.clientinfo[cgs.duelist2];
|
|
}
|
|
if (cgs.duelist3 != -1)
|
|
{
|
|
ci3 = &cgs.clientinfo[cgs.duelist3];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( i = 0 ; i < cgs.maxclients ; i++ ) {
|
|
if ( cgs.clientinfo[i].infoValid && cgs.clientinfo[i].team == TEAM_FREE ) {
|
|
if ( !ci1 ) {
|
|
ci1 = &cgs.clientinfo[i];
|
|
} else {
|
|
ci2 = &cgs.clientinfo[i];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( ci1 && ci2 )
|
|
{
|
|
if (ci3)
|
|
{
|
|
s = va( "%s vs %s and %s", ci1->name, ci2->name, ci3->name );
|
|
}
|
|
else
|
|
{
|
|
s = va( "%s vs %s", ci1->name, ci2->name );
|
|
}
|
|
w = CG_Text_Width(s, 0.6f, FONT_MEDIUM);
|
|
CG_Text_Paint(320 - w / 2, 60, 0.6f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE,FONT_MEDIUM);
|
|
}
|
|
} else {
|
|
if ( cgs.gametype == GT_FFA ) {
|
|
s = CG_GetStringEdString("MENUS", "FREE_FOR_ALL");//"Free For All";
|
|
} else if ( cgs.gametype == GT_HOLOCRON ) {
|
|
s = CG_GetStringEdString("MENUS", "HOLOCRON_FFA");//"Holocron FFA";
|
|
} else if ( cgs.gametype == GT_JEDIMASTER ) {
|
|
s = CG_GetStringEdString("MENUS", "POWERDUEL");//"Jedi Master";??
|
|
} else if ( cgs.gametype == GT_TEAM ) {
|
|
s = CG_GetStringEdString("MENUS", "TEAM_FFA");//"Team FFA";
|
|
} else if ( cgs.gametype == GT_SIEGE ) {
|
|
s = CG_GetStringEdString("MENUS", "SIEGE");//"Siege";
|
|
} else if ( cgs.gametype == GT_CTF ) {
|
|
s = CG_GetStringEdString("MENUS", "CAPTURE_THE_FLAG");//"Capture the Flag";
|
|
} else if ( cgs.gametype == GT_CTY ) {
|
|
s = CG_GetStringEdString("MENUS", "CAPTURE_THE_YSALIMARI");//"Capture the Ysalamiri";
|
|
} else {
|
|
s = "";
|
|
}
|
|
w = CG_Text_Width(s, 1.5f, FONT_MEDIUM);
|
|
CG_Text_Paint(320 - w / 2, 90, 1.5f, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE,FONT_MEDIUM);
|
|
}
|
|
|
|
sec = ( sec - cg.time ) / 1000;
|
|
if ( sec < 0 ) {
|
|
cg.warmup = 0;
|
|
sec = 0;
|
|
}
|
|
// s = va( "Starts in: %i", sec + 1 );
|
|
s = va( "%s: %i",CG_GetStringEdString("MP_INGAME", "STARTS_IN"), sec + 1 );
|
|
if ( sec != cg.warmupCount ) {
|
|
cg.warmupCount = sec;
|
|
|
|
if (cgs.gametype != GT_SIEGE)
|
|
{
|
|
switch ( sec ) {
|
|
case 0:
|
|
trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER );
|
|
break;
|
|
case 1:
|
|
trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER );
|
|
break;
|
|
case 2:
|
|
trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
scale = 0.45f;
|
|
switch ( cg.warmupCount ) {
|
|
case 0:
|
|
cw = 28;
|
|
scale = 1.25f;
|
|
break;
|
|
case 1:
|
|
cw = 24;
|
|
scale = 1.15f;
|
|
break;
|
|
case 2:
|
|
cw = 20;
|
|
scale = 1.05f;
|
|
break;
|
|
default:
|
|
cw = 16;
|
|
scale = 0.9f;
|
|
break;
|
|
}
|
|
|
|
w = CG_Text_Width(s, scale, FONT_MEDIUM);
|
|
CG_Text_Paint(320 - w / 2, 125, scale, colorWhite, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE, FONT_MEDIUM);
|
|
}
|
|
|
|
//==================================================================================
|
|
/*
|
|
=================
|
|
CG_DrawTimedMenus
|
|
=================
|
|
*/
|
|
void CG_DrawTimedMenus() {
|
|
if (cg.voiceTime) {
|
|
int t = cg.time - cg.voiceTime;
|
|
if ( t > 2500 ) {
|
|
Menus_CloseByName("voiceMenu");
|
|
trap_Cvar_Set("cl_conXOffset", "0");
|
|
cg.voiceTime = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CG_DrawFlagStatus()
|
|
{
|
|
int myFlagTakenShader = 0;
|
|
int theirFlagShader = 0;
|
|
int team = 0;
|
|
int startDrawPos = 2;
|
|
int ico_size = 32;
|
|
|
|
if (!cg.snap)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cgs.gametype != GT_CTF && cgs.gametype != GT_CTY)
|
|
{
|
|
return;
|
|
}
|
|
|
|
team = cg.snap->ps.persistant[PERS_TEAM];
|
|
|
|
if (cgs.gametype == GT_CTY)
|
|
{
|
|
if (team == TEAM_RED)
|
|
{
|
|
myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_x" );
|
|
theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_ys" );
|
|
}
|
|
else
|
|
{
|
|
myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_x" );
|
|
theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_ys" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (team == TEAM_RED)
|
|
{
|
|
myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag_x" );
|
|
theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag" );
|
|
}
|
|
else
|
|
{
|
|
myFlagTakenShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_bflag_x" );
|
|
theirFlagShader = trap_R_RegisterShaderNoMip( "gfx/hud/mpi_rflag" );
|
|
}
|
|
}
|
|
|
|
if (CG_YourTeamHasFlag())
|
|
{
|
|
//CG_DrawPic( startDrawPos, 330, ico_size, ico_size, theirFlagShader );
|
|
CG_DrawPic( 2, 330-startDrawPos, ico_size, ico_size, theirFlagShader );
|
|
startDrawPos += ico_size+2;
|
|
}
|
|
|
|
if (CG_OtherTeamHasFlag())
|
|
{
|
|
//CG_DrawPic( startDrawPos, 330, ico_size, ico_size, myFlagTakenShader );
|
|
CG_DrawPic( 2, 330-startDrawPos, ico_size, ico_size, myFlagTakenShader );
|
|
}
|
|
}
|
|
|
|
//draw meter showing jetpack fuel when it's not full
|
|
#define JPFUELBAR_H 100.0f
|
|
#define JPFUELBAR_W 20.0f
|
|
#define JPFUELBAR_X (SCREEN_WIDTH-JPFUELBAR_W-8.0f)
|
|
#define JPFUELBAR_Y 260.0f
|
|
void CG_DrawJetpackFuel(void)
|
|
{
|
|
vec4_t aColor;
|
|
vec4_t bColor;
|
|
vec4_t cColor;
|
|
float x = JPFUELBAR_X;
|
|
float y = JPFUELBAR_Y;
|
|
float percent = ((float)cg.snap->ps.jetpackFuel/100.0f)*JPFUELBAR_H;
|
|
|
|
if (percent > JPFUELBAR_H)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (percent < 0.1f)
|
|
{
|
|
percent = 0.1f;
|
|
}
|
|
|
|
//color of the bar
|
|
aColor[0] = 0.5f;
|
|
aColor[1] = 0.0f;
|
|
aColor[2] = 0.0f;
|
|
aColor[3] = 0.8f;
|
|
|
|
//color of the border
|
|
bColor[0] = 0.0f;
|
|
bColor[1] = 0.0f;
|
|
bColor[2] = 0.0f;
|
|
bColor[3] = 0.3f;
|
|
|
|
//color of greyed out "missing fuel"
|
|
cColor[0] = 0.5f;
|
|
cColor[1] = 0.5f;
|
|
cColor[2] = 0.5f;
|
|
cColor[3] = 0.1f;
|
|
|
|
//draw the background (black)
|
|
CG_DrawRect(x, y, JPFUELBAR_W, JPFUELBAR_H, 1.0f, colorTable[CT_BLACK]);
|
|
|
|
//now draw the part to show how much health there is in the color specified
|
|
CG_FillRect(x+1.0f, y+1.0f+(JPFUELBAR_H-percent), JPFUELBAR_W-1.0f, JPFUELBAR_H-1.0f-(JPFUELBAR_H-percent), aColor);
|
|
|
|
//then draw the other part greyed out
|
|
CG_FillRect(x+1.0f, y+1.0f, JPFUELBAR_W-1.0f, JPFUELBAR_H-percent, cColor);
|
|
}
|
|
|
|
//draw meter showing e-web health when it is in use
|
|
#define EWEBHEALTH_H 100.0f
|
|
#define EWEBHEALTH_W 20.0f
|
|
#define EWEBHEALTH_X (SCREEN_WIDTH-EWEBHEALTH_W-8.0f)
|
|
#define EWEBHEALTH_Y 290.0f
|
|
void CG_DrawEWebHealth(void)
|
|
{
|
|
vec4_t aColor;
|
|
vec4_t bColor;
|
|
vec4_t cColor;
|
|
float x = EWEBHEALTH_X;
|
|
float y = EWEBHEALTH_Y;
|
|
centity_t *eweb = &cg_entities[cg.predictedPlayerState.emplacedIndex];
|
|
float percent = ((float)eweb->currentState.health/eweb->currentState.maxhealth)*EWEBHEALTH_H;
|
|
|
|
if (percent > EWEBHEALTH_H)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (percent < 0.1f)
|
|
{
|
|
percent = 0.1f;
|
|
}
|
|
|
|
//kind of hacky, need to pass a coordinate in here
|
|
if (cg.snap->ps.jetpackFuel < 100)
|
|
{
|
|
x -= (JPFUELBAR_W+8.0f);
|
|
}
|
|
if (cg.snap->ps.cloakFuel < 100)
|
|
{
|
|
x -= (JPFUELBAR_W+8.0f);
|
|
}
|
|
|
|
//color of the bar
|
|
aColor[0] = 0.5f;
|
|
aColor[1] = 0.0f;
|
|
aColor[2] = 0.0f;
|
|
aColor[3] = 0.8f;
|
|
|
|
//color of the border
|
|
bColor[0] = 0.0f;
|
|
bColor[1] = 0.0f;
|
|
bColor[2] = 0.0f;
|
|
bColor[3] = 0.3f;
|
|
|
|
//color of greyed out "missing fuel"
|
|
cColor[0] = 0.5f;
|
|
cColor[1] = 0.5f;
|
|
cColor[2] = 0.5f;
|
|
cColor[3] = 0.1f;
|
|
|
|
//draw the background (black)
|
|
CG_DrawRect(x, y, EWEBHEALTH_W, EWEBHEALTH_H, 1.0f, colorTable[CT_BLACK]);
|
|
|
|
//now draw the part to show how much health there is in the color specified
|
|
CG_FillRect(x+1.0f, y+1.0f+(EWEBHEALTH_H-percent), EWEBHEALTH_W-1.0f, EWEBHEALTH_H-1.0f-(EWEBHEALTH_H-percent), aColor);
|
|
|
|
//then draw the other part greyed out
|
|
CG_FillRect(x+1.0f, y+1.0f, EWEBHEALTH_W-1.0f, EWEBHEALTH_H-percent, cColor);
|
|
}
|
|
|
|
//draw meter showing cloak fuel when it's not full
|
|
#define CLFUELBAR_H 100.0f
|
|
#define CLFUELBAR_W 20.0f
|
|
#define CLFUELBAR_X (SCREEN_WIDTH-CLFUELBAR_W-8.0f)
|
|
#define CLFUELBAR_Y 260.0f
|
|
void CG_DrawCloakFuel(void)
|
|
{
|
|
vec4_t aColor;
|
|
vec4_t bColor;
|
|
vec4_t cColor;
|
|
float x = CLFUELBAR_X;
|
|
float y = CLFUELBAR_Y;
|
|
float percent = ((float)cg.snap->ps.cloakFuel/100.0f)*CLFUELBAR_H;
|
|
|
|
if (percent > CLFUELBAR_H)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.snap->ps.jetpackFuel < 100 )
|
|
{//if drawing jetpack fuel bar too, then move this over...?
|
|
x -= (JPFUELBAR_W+8.0f);
|
|
}
|
|
|
|
if (percent < 0.1f)
|
|
{
|
|
percent = 0.1f;
|
|
}
|
|
|
|
//color of the bar
|
|
aColor[0] = 0.0f;
|
|
aColor[1] = 0.0f;
|
|
aColor[2] = 0.6f;
|
|
aColor[3] = 0.8f;
|
|
|
|
//color of the border
|
|
bColor[0] = 0.0f;
|
|
bColor[1] = 0.0f;
|
|
bColor[2] = 0.0f;
|
|
bColor[3] = 0.3f;
|
|
|
|
//color of greyed out "missing fuel"
|
|
cColor[0] = 0.1f;
|
|
cColor[1] = 0.1f;
|
|
cColor[2] = 0.3f;
|
|
cColor[3] = 0.1f;
|
|
|
|
//draw the background (black)
|
|
CG_DrawRect(x, y, CLFUELBAR_W, CLFUELBAR_H, 1.0f, colorTable[CT_BLACK]);
|
|
|
|
//now draw the part to show how much fuel there is in the color specified
|
|
CG_FillRect(x+1.0f, y+1.0f+(CLFUELBAR_H-percent), CLFUELBAR_W-1.0f, CLFUELBAR_H-1.0f-(CLFUELBAR_H-percent), aColor);
|
|
|
|
//then draw the other part greyed out
|
|
CG_FillRect(x+1.0f, y+1.0f, CLFUELBAR_W-1.0f, CLFUELBAR_H-percent, cColor);
|
|
}
|
|
|
|
int cgRageTime = 0;
|
|
int cgRageFadeTime = 0;
|
|
float cgRageFadeVal = 0;
|
|
|
|
int cgRageRecTime = 0;
|
|
int cgRageRecFadeTime = 0;
|
|
float cgRageRecFadeVal = 0;
|
|
|
|
int cgAbsorbTime = 0;
|
|
int cgAbsorbFadeTime = 0;
|
|
float cgAbsorbFadeVal = 0;
|
|
|
|
int cgProtectTime = 0;
|
|
int cgProtectFadeTime = 0;
|
|
float cgProtectFadeVal = 0;
|
|
|
|
int cgYsalTime = 0;
|
|
int cgYsalFadeTime = 0;
|
|
float cgYsalFadeVal = 0;
|
|
|
|
qboolean gCGHasFallVector = qfalse;
|
|
vec3_t gCGFallVector;
|
|
|
|
/*
|
|
=================
|
|
CG_Draw2D
|
|
=================
|
|
*/
|
|
extern int cgSiegeRoundState;
|
|
extern int cgSiegeRoundTime;
|
|
|
|
extern int team1Timed;
|
|
extern int team2Timed;
|
|
|
|
int cg_beatingSiegeTime = 0;
|
|
|
|
int cgSiegeRoundBeganTime = 0;
|
|
int cgSiegeRoundCountTime = 0;
|
|
|
|
static void CG_DrawSiegeTimer(int timeRemaining, qboolean isMyTeam)
|
|
{ //rwwFIXMEFIXME: Make someone make assets and use them.
|
|
//this function is pretty much totally placeholder.
|
|
// int x = 0;
|
|
// int y = SCREEN_HEIGHT-160;
|
|
int fColor = 0;
|
|
int minutes = 0;
|
|
int seconds = 0;
|
|
char timeStr[1024];
|
|
menuDef_t *menuHUD = NULL;
|
|
itemDef_t *item = NULL;
|
|
|
|
menuHUD = Menus_FindByName("mp_timer");
|
|
if (!menuHUD)
|
|
{
|
|
return;
|
|
}
|
|
|
|
item = Menu_FindItemByName(menuHUD, "frame");
|
|
|
|
if (item)
|
|
{
|
|
trap_R_SetColor( item->window.foreColor );
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
}
|
|
|
|
seconds = timeRemaining;
|
|
|
|
while (seconds >= 60)
|
|
{
|
|
minutes++;
|
|
seconds -= 60;
|
|
}
|
|
|
|
strcpy(timeStr, va( "%i:%02i", minutes, seconds ));
|
|
|
|
if (isMyTeam)
|
|
{
|
|
fColor = CT_HUD_RED;
|
|
}
|
|
else
|
|
{
|
|
fColor = CT_HUD_GREEN;
|
|
}
|
|
|
|
// trap_Cvar_Set("ui_siegeTimer", timeStr);
|
|
|
|
// UI_DrawProportionalString( x+16, y+40, timeStr,
|
|
// UI_SMALLFONT|UI_DROPSHADOW, colorTable[fColor] );
|
|
|
|
item = Menu_FindItemByName(menuHUD, "timer");
|
|
if (item)
|
|
{
|
|
UI_DrawProportionalString(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
timeStr,
|
|
UI_SMALLFONT|UI_DROPSHADOW,
|
|
colorTable[fColor] );
|
|
}
|
|
|
|
}
|
|
|
|
static void CG_DrawSiegeDeathTimer( int timeRemaining )
|
|
{
|
|
int minutes = 0;
|
|
int seconds = 0;
|
|
char timeStr[1024];
|
|
menuDef_t *menuHUD = NULL;
|
|
itemDef_t *item = NULL;
|
|
|
|
menuHUD = Menus_FindByName("mp_timer");
|
|
if (!menuHUD)
|
|
{
|
|
return;
|
|
}
|
|
|
|
item = Menu_FindItemByName(menuHUD, "frame");
|
|
|
|
if (item)
|
|
{
|
|
trap_R_SetColor( item->window.foreColor );
|
|
CG_DrawPic(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
item->window.rect.w,
|
|
item->window.rect.h,
|
|
item->window.background );
|
|
}
|
|
|
|
seconds = timeRemaining;
|
|
|
|
while (seconds >= 60)
|
|
{
|
|
minutes++;
|
|
seconds -= 60;
|
|
}
|
|
|
|
if (seconds < 10)
|
|
{
|
|
strcpy(timeStr, va( "%i:0%i", minutes, seconds ));
|
|
}
|
|
else
|
|
{
|
|
strcpy(timeStr, va( "%i:%i", minutes, seconds ));
|
|
}
|
|
|
|
item = Menu_FindItemByName(menuHUD, "deathtimer");
|
|
if (item)
|
|
{
|
|
UI_DrawProportionalString(
|
|
item->window.rect.x,
|
|
item->window.rect.y,
|
|
timeStr,
|
|
UI_SMALLFONT|UI_DROPSHADOW,
|
|
item->window.foreColor );
|
|
}
|
|
|
|
}
|
|
|
|
int cgSiegeEntityRender = 0;
|
|
|
|
static void CG_DrawSiegeHUDItem(void)
|
|
{
|
|
void *g2;
|
|
qhandle_t handle;
|
|
vec3_t origin, angles;
|
|
vec3_t mins, maxs;
|
|
float len;
|
|
centity_t *cent = &cg_entities[cgSiegeEntityRender];
|
|
|
|
if (cent->ghoul2)
|
|
{
|
|
g2 = cent->ghoul2;
|
|
handle = 0;
|
|
}
|
|
else
|
|
{
|
|
handle = cgs.gameModels[cent->currentState.modelindex];
|
|
g2 = NULL;
|
|
}
|
|
|
|
if (handle)
|
|
{
|
|
trap_R_ModelBounds( handle, mins, maxs );
|
|
}
|
|
else
|
|
{
|
|
VectorSet(mins, -16, -16, -20);
|
|
VectorSet(maxs, 16, 16, 32);
|
|
}
|
|
|
|
origin[2] = -0.5 * ( mins[2] + maxs[2] );
|
|
origin[1] = 0.5 * ( mins[1] + maxs[1] );
|
|
len = 0.5 * ( maxs[2] - mins[2] );
|
|
origin[0] = len / 0.268;
|
|
|
|
VectorClear(angles);
|
|
angles[YAW] = cg.autoAngles[YAW];
|
|
|
|
CG_Draw3DModel( 8, 8, 64, 64, handle, g2, cent->currentState.g2radius, 0, origin, angles );
|
|
|
|
cgSiegeEntityRender = 0; //reset for next frame
|
|
}
|
|
|
|
/*====================================
|
|
chatbox functionality -rww
|
|
====================================*/
|
|
#define CHATBOX_CUTOFF_LEN 550
|
|
#define CHATBOX_FONT_HEIGHT 20
|
|
|
|
//utility func, insert a string into a string at the specified
|
|
//place (assuming this will not overflow the buffer)
|
|
void CG_ChatBox_StrInsert(char *buffer, int place, char *str)
|
|
{
|
|
int insLen = strlen(str);
|
|
int i = strlen(buffer);
|
|
int k = 0;
|
|
|
|
buffer[i+insLen+1] = 0; //terminate the string at its new length
|
|
while (i >= place)
|
|
{
|
|
buffer[i+insLen] = buffer[i];
|
|
i--;
|
|
}
|
|
|
|
i++;
|
|
while (k < insLen)
|
|
{
|
|
buffer[i] = str[k];
|
|
i++;
|
|
k++;
|
|
}
|
|
}
|
|
|
|
//add chatbox string
|
|
void CG_ChatBox_AddString(char *chatStr)
|
|
{
|
|
chatBoxItem_t *chat = &cg.chatItems[cg.chatItemActive];
|
|
float chatLen;
|
|
|
|
if (cg_chatBox.integer<=0)
|
|
{ //don't bother then.
|
|
return;
|
|
}
|
|
|
|
memset(chat, 0, sizeof(chatBoxItem_t));
|
|
|
|
if (strlen(chatStr) > sizeof(chat->string))
|
|
{ //too long, terminate at proper len.
|
|
chatStr[sizeof(chat->string)-1] = 0;
|
|
}
|
|
|
|
strcpy(chat->string, chatStr);
|
|
chat->time = cg.time + cg_chatBox.integer;
|
|
|
|
chat->lines = 1;
|
|
|
|
chatLen = CG_Text_Width(chat->string, 1.0f, FONT_SMALL);
|
|
if (chatLen > CHATBOX_CUTOFF_LEN)
|
|
{ //we have to break it into segments...
|
|
int i = 0;
|
|
int lastLinePt = 0;
|
|
char s[2];
|
|
|
|
chatLen = 0;
|
|
while (chat->string[i])
|
|
{
|
|
s[0] = chat->string[i];
|
|
s[1] = 0;
|
|
chatLen += CG_Text_Width(s, 0.65f, FONT_SMALL);
|
|
|
|
if (chatLen >= CHATBOX_CUTOFF_LEN)
|
|
{
|
|
int j = i;
|
|
while (j > 0 && j > lastLinePt)
|
|
{
|
|
if (chat->string[j] == ' ')
|
|
{
|
|
break;
|
|
}
|
|
j--;
|
|
}
|
|
if (chat->string[j] == ' ')
|
|
{
|
|
i = j;
|
|
}
|
|
|
|
chat->lines++;
|
|
CG_ChatBox_StrInsert(chat->string, i, "\n");
|
|
i++;
|
|
chatLen = 0;
|
|
lastLinePt = i+1;
|
|
}
|
|
i++;
|
|
}
|
|
}
|
|
|
|
cg.chatItemActive++;
|
|
if (cg.chatItemActive >= MAX_CHATBOX_ITEMS)
|
|
{
|
|
cg.chatItemActive = 0;
|
|
}
|
|
}
|
|
|
|
//insert item into array (rearranging the array if necessary)
|
|
void CG_ChatBox_ArrayInsert(chatBoxItem_t **array, int insPoint, int maxNum, chatBoxItem_t *item)
|
|
{
|
|
if (array[insPoint])
|
|
{ //recursively call, to move everything up to the top
|
|
if (insPoint+1 >= maxNum)
|
|
{
|
|
CG_Error("CG_ChatBox_ArrayInsert: Exceeded array size");
|
|
}
|
|
CG_ChatBox_ArrayInsert(array, insPoint+1, maxNum, array[insPoint]);
|
|
}
|
|
|
|
//now that we have moved anything that would be in this slot up, insert what we want into the slot
|
|
array[insPoint] = item;
|
|
}
|
|
|
|
//go through all the chat strings and draw them if they are not yet expired
|
|
static CGAME_INLINE void CG_ChatBox_DrawStrings(void)
|
|
{
|
|
chatBoxItem_t *drawThese[MAX_CHATBOX_ITEMS];
|
|
int numToDraw = 0;
|
|
int linesToDraw = 0;
|
|
int i = 0;
|
|
int x = 30;
|
|
int y = cg.scoreBoardShowing ? 475 : cg_chatBoxHeight.integer;
|
|
float fontScale = 0.65f;
|
|
|
|
if (!cg_chatBox.integer)
|
|
{
|
|
return;
|
|
}
|
|
|
|
memset(drawThese, 0, sizeof(drawThese));
|
|
|
|
while (i < MAX_CHATBOX_ITEMS)
|
|
{
|
|
if (cg.chatItems[i].time >= cg.time)
|
|
{
|
|
int check = numToDraw;
|
|
int insertionPoint = numToDraw;
|
|
|
|
while (check >= 0)
|
|
{
|
|
if (drawThese[check] &&
|
|
cg.chatItems[i].time < drawThese[check]->time)
|
|
{ //insert here
|
|
insertionPoint = check;
|
|
}
|
|
check--;
|
|
}
|
|
CG_ChatBox_ArrayInsert(drawThese, insertionPoint, MAX_CHATBOX_ITEMS, &cg.chatItems[i]);
|
|
numToDraw++;
|
|
linesToDraw += cg.chatItems[i].lines;
|
|
}
|
|
i++;
|
|
}
|
|
|
|
if (!numToDraw)
|
|
{ //nothing, then, just get out of here now.
|
|
return;
|
|
}
|
|
|
|
//move initial point up so we draw bottom-up (visually)
|
|
y -= (CHATBOX_FONT_HEIGHT*fontScale)*linesToDraw;
|
|
|
|
//we have the items we want to draw, just quickly loop through them now
|
|
i = 0;
|
|
while (i < numToDraw)
|
|
{
|
|
CG_Text_Paint(x, y, fontScale, colorWhite, drawThese[i]->string, 0, 0, ITEM_TEXTSTYLE_OUTLINED, FONT_SMALL );
|
|
y += ((CHATBOX_FONT_HEIGHT*fontScale)*drawThese[i]->lines);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
static void CG_Draw2DScreenTints( void )
|
|
{
|
|
float rageTime, rageRecTime, absorbTime, protectTime, ysalTime;
|
|
vec4_t hcolor;
|
|
if (cgs.clientinfo[cg.snap->ps.clientNum].team != TEAM_SPECTATOR)
|
|
{
|
|
if (cg.snap->ps.fd.forcePowersActive & (1 << FP_RAGE))
|
|
{
|
|
if (!cgRageTime)
|
|
{
|
|
cgRageTime = cg.time;
|
|
}
|
|
|
|
rageTime = (float)(cg.time - cgRageTime);
|
|
|
|
rageTime /= 9000;
|
|
|
|
if (rageTime < 0)
|
|
{
|
|
rageTime = 0;
|
|
}
|
|
if (rageTime > 0.15)
|
|
{
|
|
rageTime = 0.15;
|
|
}
|
|
|
|
hcolor[3] = rageTime;
|
|
hcolor[0] = 0.7;
|
|
hcolor[1] = 0;
|
|
hcolor[2] = 0;
|
|
|
|
if (!cg.renderingThirdPerson)
|
|
{
|
|
CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor);
|
|
}
|
|
|
|
cgRageFadeTime = 0;
|
|
cgRageFadeVal = 0;
|
|
}
|
|
else if (cgRageTime)
|
|
{
|
|
if (!cgRageFadeTime)
|
|
{
|
|
cgRageFadeTime = cg.time;
|
|
cgRageFadeVal = 0.15;
|
|
}
|
|
|
|
rageTime = cgRageFadeVal;
|
|
|
|
cgRageFadeVal -= (cg.time - cgRageFadeTime)*0.000005;
|
|
|
|
if (rageTime < 0)
|
|
{
|
|
rageTime = 0;
|
|
}
|
|
if (rageTime > 0.15)
|
|
{
|
|
rageTime = 0.15;
|
|
}
|
|
|
|
if (cg.snap->ps.fd.forceRageRecoveryTime > cg.time)
|
|
{
|
|
float checkRageRecTime = rageTime;
|
|
|
|
if (checkRageRecTime < 0.15)
|
|
{
|
|
checkRageRecTime = 0.15;
|
|
}
|
|
|
|
hcolor[3] = checkRageRecTime;
|
|
hcolor[0] = rageTime*4;
|
|
if (hcolor[0] < 0.2)
|
|
{
|
|
hcolor[0] = 0.2;
|
|
}
|
|
hcolor[1] = 0.2;
|
|
hcolor[2] = 0.2;
|
|
}
|
|
else
|
|
{
|
|
hcolor[3] = rageTime;
|
|
hcolor[0] = 0.7;
|
|
hcolor[1] = 0;
|
|
hcolor[2] = 0;
|
|
}
|
|
|
|
if (!cg.renderingThirdPerson && rageTime)
|
|
{
|
|
CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor);
|
|
}
|
|
else
|
|
{
|
|
if (cg.snap->ps.fd.forceRageRecoveryTime > cg.time)
|
|
{
|
|
hcolor[3] = 0.15;
|
|
hcolor[0] = 0.2;
|
|
hcolor[1] = 0.2;
|
|
hcolor[2] = 0.2;
|
|
CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor);
|
|
}
|
|
cgRageTime = 0;
|
|
}
|
|
}
|
|
else if (cg.snap->ps.fd.forceRageRecoveryTime > cg.time)
|
|
{
|
|
if (!cgRageRecTime)
|
|
{
|
|
cgRageRecTime = cg.time;
|
|
}
|
|
|
|
rageRecTime = (float)(cg.time - cgRageRecTime);
|
|
|
|
rageRecTime /= 9000;
|
|
|
|
if (rageRecTime < 0.15)//0)
|
|
{
|
|
rageRecTime = 0.15;//0;
|
|
}
|
|
if (rageRecTime > 0.15)
|
|
{
|
|
rageRecTime = 0.15;
|
|
}
|
|
|
|
hcolor[3] = rageRecTime;
|
|
hcolor[0] = 0.2;
|
|
hcolor[1] = 0.2;
|
|
hcolor[2] = 0.2;
|
|
|
|
if (!cg.renderingThirdPerson)
|
|
{
|
|
CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor);
|
|
}
|
|
|
|
cgRageRecFadeTime = 0;
|
|
cgRageRecFadeVal = 0;
|
|
}
|
|
else if (cgRageRecTime)
|
|
{
|
|
if (!cgRageRecFadeTime)
|
|
{
|
|
cgRageRecFadeTime = cg.time;
|
|
cgRageRecFadeVal = 0.15;
|
|
}
|
|
|
|
rageRecTime = cgRageRecFadeVal;
|
|
|
|
cgRageRecFadeVal -= (cg.time - cgRageRecFadeTime)*0.000005;
|
|
|
|
if (rageRecTime < 0)
|
|
{
|
|
rageRecTime = 0;
|
|
}
|
|
if (rageRecTime > 0.15)
|
|
{
|
|
rageRecTime = 0.15;
|
|
}
|
|
|
|
hcolor[3] = rageRecTime;
|
|
hcolor[0] = 0.2;
|
|
hcolor[1] = 0.2;
|
|
hcolor[2] = 0.2;
|
|
|
|
if (!cg.renderingThirdPerson && rageRecTime)
|
|
{
|
|
CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor);
|
|
}
|
|
else
|
|
{
|
|
cgRageRecTime = 0;
|
|
}
|
|
}
|
|
|
|
if (cg.snap->ps.fd.forcePowersActive & (1 << FP_ABSORB))
|
|
{
|
|
if (!cgAbsorbTime)
|
|
{
|
|
cgAbsorbTime = cg.time;
|
|
}
|
|
|
|
absorbTime = (float)(cg.time - cgAbsorbTime);
|
|
|
|
absorbTime /= 9000;
|
|
|
|
if (absorbTime < 0)
|
|
{
|
|
absorbTime = 0;
|
|
}
|
|
if (absorbTime > 0.15)
|
|
{
|
|
absorbTime = 0.15;
|
|
}
|
|
|
|
hcolor[3] = absorbTime/2;
|
|
hcolor[0] = 0;
|
|
hcolor[1] = 0;
|
|
hcolor[2] = 0.7;
|
|
|
|
if (!cg.renderingThirdPerson)
|
|
{
|
|
CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor);
|
|
}
|
|
|
|
cgAbsorbFadeTime = 0;
|
|
cgAbsorbFadeVal = 0;
|
|
}
|
|
else if (cgAbsorbTime)
|
|
{
|
|
if (!cgAbsorbFadeTime)
|
|
{
|
|
cgAbsorbFadeTime = cg.time;
|
|
cgAbsorbFadeVal = 0.15;
|
|
}
|
|
|
|
absorbTime = cgAbsorbFadeVal;
|
|
|
|
cgAbsorbFadeVal -= (cg.time - cgAbsorbFadeTime)*0.000005;
|
|
|
|
if (absorbTime < 0)
|
|
{
|
|
absorbTime = 0;
|
|
}
|
|
if (absorbTime > 0.15)
|
|
{
|
|
absorbTime = 0.15;
|
|
}
|
|
|
|
hcolor[3] = absorbTime/2;
|
|
hcolor[0] = 0;
|
|
hcolor[1] = 0;
|
|
hcolor[2] = 0.7;
|
|
|
|
if (!cg.renderingThirdPerson && absorbTime)
|
|
{
|
|
CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor);
|
|
}
|
|
else
|
|
{
|
|
cgAbsorbTime = 0;
|
|
}
|
|
}
|
|
|
|
if (cg.snap->ps.fd.forcePowersActive & (1 << FP_PROTECT))
|
|
{
|
|
if (!cgProtectTime)
|
|
{
|
|
cgProtectTime = cg.time;
|
|
}
|
|
|
|
protectTime = (float)(cg.time - cgProtectTime);
|
|
|
|
protectTime /= 9000;
|
|
|
|
if (protectTime < 0)
|
|
{
|
|
protectTime = 0;
|
|
}
|
|
if (protectTime > 0.15)
|
|
{
|
|
protectTime = 0.15;
|
|
}
|
|
|
|
hcolor[3] = protectTime/2;
|
|
hcolor[0] = 0;
|
|
hcolor[1] = 0.7;
|
|
hcolor[2] = 0;
|
|
|
|
if (!cg.renderingThirdPerson)
|
|
{
|
|
CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor);
|
|
}
|
|
|
|
cgProtectFadeTime = 0;
|
|
cgProtectFadeVal = 0;
|
|
}
|
|
else if (cgProtectTime)
|
|
{
|
|
if (!cgProtectFadeTime)
|
|
{
|
|
cgProtectFadeTime = cg.time;
|
|
cgProtectFadeVal = 0.15;
|
|
}
|
|
|
|
protectTime = cgProtectFadeVal;
|
|
|
|
cgProtectFadeVal -= (cg.time - cgProtectFadeTime)*0.000005;
|
|
|
|
if (protectTime < 0)
|
|
{
|
|
protectTime = 0;
|
|
}
|
|
if (protectTime > 0.15)
|
|
{
|
|
protectTime = 0.15;
|
|
}
|
|
|
|
hcolor[3] = protectTime/2;
|
|
hcolor[0] = 0;
|
|
hcolor[1] = 0.7;
|
|
hcolor[2] = 0;
|
|
|
|
if (!cg.renderingThirdPerson && protectTime)
|
|
{
|
|
CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor);
|
|
}
|
|
else
|
|
{
|
|
cgProtectTime = 0;
|
|
}
|
|
}
|
|
|
|
if (cg.snap->ps.rocketLockIndex != ENTITYNUM_NONE && (cg.time - cg.snap->ps.rocketLockTime) > 0)
|
|
{
|
|
CG_DrawRocketLocking( cg.snap->ps.rocketLockIndex, cg.snap->ps.rocketLockTime );
|
|
}
|
|
|
|
if (BG_HasYsalamiri(cgs.gametype, &cg.snap->ps))
|
|
{
|
|
if (!cgYsalTime)
|
|
{
|
|
cgYsalTime = cg.time;
|
|
}
|
|
|
|
ysalTime = (float)(cg.time - cgYsalTime);
|
|
|
|
ysalTime /= 9000;
|
|
|
|
if (ysalTime < 0)
|
|
{
|
|
ysalTime = 0;
|
|
}
|
|
if (ysalTime > 0.15)
|
|
{
|
|
ysalTime = 0.15;
|
|
}
|
|
|
|
hcolor[3] = ysalTime/2;
|
|
hcolor[0] = 0.7;
|
|
hcolor[1] = 0.7;
|
|
hcolor[2] = 0;
|
|
|
|
if (!cg.renderingThirdPerson)
|
|
{
|
|
CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor);
|
|
}
|
|
|
|
cgYsalFadeTime = 0;
|
|
cgYsalFadeVal = 0;
|
|
}
|
|
else if (cgYsalTime)
|
|
{
|
|
if (!cgYsalFadeTime)
|
|
{
|
|
cgYsalFadeTime = cg.time;
|
|
cgYsalFadeVal = 0.15;
|
|
}
|
|
|
|
ysalTime = cgYsalFadeVal;
|
|
|
|
cgYsalFadeVal -= (cg.time - cgYsalFadeTime)*0.000005;
|
|
|
|
if (ysalTime < 0)
|
|
{
|
|
ysalTime = 0;
|
|
}
|
|
if (ysalTime > 0.15)
|
|
{
|
|
ysalTime = 0.15;
|
|
}
|
|
|
|
hcolor[3] = ysalTime/2;
|
|
hcolor[0] = 0.7;
|
|
hcolor[1] = 0.7;
|
|
hcolor[2] = 0;
|
|
|
|
if (!cg.renderingThirdPerson && ysalTime)
|
|
{
|
|
CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor);
|
|
}
|
|
else
|
|
{
|
|
cgYsalTime = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( (cg.refdef.viewContents&CONTENTS_LAVA) )
|
|
{//tint screen red
|
|
float phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2;
|
|
hcolor[3] = 0.5 + (0.15f*sin( phase ));
|
|
hcolor[0] = 0.7f;
|
|
hcolor[1] = 0;
|
|
hcolor[2] = 0;
|
|
|
|
CG_DrawRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor );
|
|
}
|
|
else if ( (cg.refdef.viewContents&CONTENTS_SLIME) )
|
|
{//tint screen green
|
|
float phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2;
|
|
hcolor[3] = 0.4 + (0.1f*sin( phase ));
|
|
hcolor[0] = 0;
|
|
hcolor[1] = 0.7f;
|
|
hcolor[2] = 0;
|
|
|
|
CG_DrawRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor );
|
|
}
|
|
else if ( (cg.refdef.viewContents&CONTENTS_WATER) )
|
|
{//tint screen light blue -- FIXME: don't do this if CONTENTS_FOG? (in case someone *does* make a water shader with fog in it?)
|
|
float phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2;
|
|
hcolor[3] = 0.3 + (0.05f*sin( phase ));
|
|
hcolor[0] = 0;
|
|
hcolor[1] = 0.2f;
|
|
hcolor[2] = 0.8;
|
|
|
|
CG_DrawRect( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor );
|
|
}
|
|
}
|
|
|
|
static void CG_Draw2D( void ) {
|
|
float inTime = cg.invenSelectTime+WEAPON_SELECT_TIME;
|
|
float wpTime = cg.weaponSelectTime+WEAPON_SELECT_TIME;
|
|
float fallTime;
|
|
float bestTime;
|
|
int drawSelect = 0;
|
|
|
|
// if we are taking a levelshot for the menu, don't draw anything
|
|
if ( cg.levelShot ) {
|
|
return;
|
|
}
|
|
|
|
if (cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR)
|
|
{
|
|
cgRageTime = 0;
|
|
cgRageFadeTime = 0;
|
|
cgRageFadeVal = 0;
|
|
|
|
cgRageRecTime = 0;
|
|
cgRageRecFadeTime = 0;
|
|
cgRageRecFadeVal = 0;
|
|
|
|
cgAbsorbTime = 0;
|
|
cgAbsorbFadeTime = 0;
|
|
cgAbsorbFadeVal = 0;
|
|
|
|
cgProtectTime = 0;
|
|
cgProtectFadeTime = 0;
|
|
cgProtectFadeVal = 0;
|
|
|
|
cgYsalTime = 0;
|
|
cgYsalFadeTime = 0;
|
|
cgYsalFadeVal = 0;
|
|
}
|
|
|
|
if ( cg_draw2D.integer == 0 ) {
|
|
return;
|
|
}
|
|
|
|
if ( cg.snap->ps.pm_type == PM_INTERMISSION ) {
|
|
CG_DrawIntermission();
|
|
CG_ChatBox_DrawStrings();
|
|
return;
|
|
}
|
|
|
|
CG_Draw2DScreenTints();
|
|
|
|
if (cg.snap->ps.rocketLockIndex != ENTITYNUM_NONE && (cg.time - cg.snap->ps.rocketLockTime) > 0)
|
|
{
|
|
CG_DrawRocketLocking( cg.snap->ps.rocketLockIndex, cg.snap->ps.rocketLockTime );
|
|
}
|
|
|
|
if (cg.snap->ps.holocronBits)
|
|
{
|
|
CG_DrawHolocronIcons();
|
|
}
|
|
if (cg.snap->ps.fd.forcePowersActive || cg.snap->ps.fd.forceRageRecoveryTime > cg.time)
|
|
{
|
|
CG_DrawActivePowers();
|
|
}
|
|
|
|
if (cg.snap->ps.jetpackFuel < 100)
|
|
{ //draw it as long as it isn't full
|
|
CG_DrawJetpackFuel();
|
|
}
|
|
if (cg.snap->ps.cloakFuel < 100)
|
|
{ //draw it as long as it isn't full
|
|
CG_DrawCloakFuel();
|
|
}
|
|
if (cg.predictedPlayerState.emplacedIndex > 0)
|
|
{
|
|
centity_t *eweb = &cg_entities[cg.predictedPlayerState.emplacedIndex];
|
|
|
|
if (eweb->currentState.weapon == WP_NONE)
|
|
{ //using an e-web, draw its health
|
|
CG_DrawEWebHealth();
|
|
}
|
|
}
|
|
|
|
// Draw this before the text so that any text won't get clipped off
|
|
CG_DrawZoomMask();
|
|
|
|
/*
|
|
if (cg.cameraMode) {
|
|
return;
|
|
}
|
|
*/
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR ) {
|
|
CG_DrawSpectator();
|
|
CG_DrawCrosshair(NULL, 0);
|
|
CG_DrawCrosshairNames();
|
|
CG_SaberClashFlare();
|
|
} else {
|
|
// don't draw any status if dead or the scoreboard is being explicitly shown
|
|
if ( !cg.showScores && cg.snap->ps.stats[STAT_HEALTH] > 0 ) {
|
|
|
|
if ( /*cg_drawStatus.integer*/0 ) {
|
|
//Reenable if stats are drawn with menu system again
|
|
Menu_PaintAll();
|
|
CG_DrawTimedMenus();
|
|
}
|
|
|
|
//CG_DrawTemporaryStats();
|
|
|
|
CG_DrawAmmoWarning();
|
|
|
|
CG_DrawCrosshairNames();
|
|
|
|
if (cg_drawStatus.integer)
|
|
{
|
|
CG_DrawIconBackground();
|
|
}
|
|
|
|
if (inTime > wpTime)
|
|
{
|
|
drawSelect = 1;
|
|
bestTime = cg.invenSelectTime;
|
|
}
|
|
else //only draw the most recent since they're drawn in the same place
|
|
{
|
|
drawSelect = 2;
|
|
bestTime = cg.weaponSelectTime;
|
|
}
|
|
|
|
if (cg.forceSelectTime > bestTime)
|
|
{
|
|
drawSelect = 3;
|
|
}
|
|
|
|
switch(drawSelect)
|
|
{
|
|
case 1:
|
|
CG_DrawInvenSelect();
|
|
break;
|
|
case 2:
|
|
CG_DrawWeaponSelect();
|
|
break;
|
|
case 3:
|
|
CG_DrawForceSelect();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (cg_drawStatus.integer)
|
|
{
|
|
//Powerups now done with upperright stuff
|
|
//CG_DrawPowerupIcons();
|
|
|
|
CG_DrawFlagStatus();
|
|
}
|
|
|
|
CG_SaberClashFlare();
|
|
|
|
if (cg_drawStatus.integer)
|
|
{
|
|
CG_DrawStats();
|
|
}
|
|
|
|
CG_DrawPickupItem();
|
|
//Do we want to use this system again at some point?
|
|
//CG_DrawReward();
|
|
}
|
|
|
|
}
|
|
|
|
if (cg.snap->ps.fallingToDeath)
|
|
{
|
|
vec4_t hcolor;
|
|
|
|
fallTime = (float)(cg.time - cg.snap->ps.fallingToDeath);
|
|
|
|
fallTime /= (FALL_FADE_TIME/2);
|
|
|
|
if (fallTime < 0)
|
|
{
|
|
fallTime = 0;
|
|
}
|
|
if (fallTime > 1)
|
|
{
|
|
fallTime = 1;
|
|
}
|
|
|
|
hcolor[3] = fallTime;
|
|
hcolor[0] = 0;
|
|
hcolor[1] = 0;
|
|
hcolor[2] = 0;
|
|
|
|
CG_DrawRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH*SCREEN_HEIGHT, hcolor);
|
|
|
|
if (!gCGHasFallVector)
|
|
{
|
|
VectorCopy(cg.snap->ps.origin, gCGFallVector);
|
|
gCGHasFallVector = qtrue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (gCGHasFallVector)
|
|
{
|
|
gCGHasFallVector = qfalse;
|
|
VectorClear(gCGFallVector);
|
|
}
|
|
}
|
|
|
|
CG_DrawVote();
|
|
CG_DrawTeamVote();
|
|
|
|
CG_DrawLagometer();
|
|
|
|
|
|
if (!cg_paused.integer) {
|
|
CG_DrawBracketedEntities();
|
|
CG_DrawUpperRight();
|
|
}
|
|
|
|
if ( !CG_DrawFollow() ) {
|
|
CG_DrawWarmup();
|
|
}
|
|
|
|
if (cgSiegeRoundState)
|
|
{
|
|
char pStr[1024];
|
|
int rTime = 0;
|
|
|
|
//cgSiegeRoundBeganTime = 0;
|
|
|
|
switch (cgSiegeRoundState)
|
|
{
|
|
case 1:
|
|
CG_CenterPrint(CG_GetStringEdString("MP_INGAME", "WAITING_FOR_PLAYERS"), SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH);
|
|
break;
|
|
case 2:
|
|
rTime = (SIEGE_ROUND_BEGIN_TIME - (cg.time - cgSiegeRoundTime));
|
|
|
|
if (rTime < 0)
|
|
{
|
|
rTime = 0;
|
|
}
|
|
if (rTime > SIEGE_ROUND_BEGIN_TIME)
|
|
{
|
|
rTime = SIEGE_ROUND_BEGIN_TIME;
|
|
}
|
|
|
|
rTime /= 1000;
|
|
|
|
rTime += 1;
|
|
|
|
if (rTime < 1)
|
|
{
|
|
rTime = 1;
|
|
}
|
|
|
|
if (rTime <= 3 && rTime != cgSiegeRoundCountTime)
|
|
{
|
|
cgSiegeRoundCountTime = rTime;
|
|
|
|
switch (rTime)
|
|
{
|
|
case 1:
|
|
trap_S_StartLocalSound( cgs.media.count1Sound, CHAN_ANNOUNCER );
|
|
break;
|
|
case 2:
|
|
trap_S_StartLocalSound( cgs.media.count2Sound, CHAN_ANNOUNCER );
|
|
break;
|
|
case 3:
|
|
trap_S_StartLocalSound( cgs.media.count3Sound, CHAN_ANNOUNCER );
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
strcpy(pStr, va("%s %i...", CG_GetStringEdString("MP_INGAME", "ROUNDBEGINSIN"), rTime));
|
|
CG_CenterPrint(pStr, SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH);
|
|
//same
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
cgSiegeEntityRender = 0;
|
|
}
|
|
else if (cgSiegeRoundTime)
|
|
{
|
|
CG_CenterPrint("", SCREEN_HEIGHT * 0.30, BIGCHAR_WIDTH);
|
|
cgSiegeRoundTime = 0;
|
|
|
|
//cgSiegeRoundBeganTime = cg.time;
|
|
cgSiegeEntityRender = 0;
|
|
}
|
|
else if (cgSiegeRoundBeganTime)
|
|
{ //Draw how much time is left in the round based on local info.
|
|
int timedTeam = TEAM_FREE;
|
|
int timedValue = 0;
|
|
|
|
if (cgSiegeEntityRender)
|
|
{ //render the objective item model since this client has it
|
|
CG_DrawSiegeHUDItem();
|
|
}
|
|
|
|
if (team1Timed)
|
|
{
|
|
timedTeam = TEAM_RED; //team 1
|
|
if (cg_beatingSiegeTime)
|
|
{
|
|
timedValue = cg_beatingSiegeTime;
|
|
}
|
|
else
|
|
{
|
|
timedValue = team1Timed;
|
|
}
|
|
}
|
|
else if (team2Timed)
|
|
{
|
|
timedTeam = TEAM_BLUE; //team 2
|
|
if (cg_beatingSiegeTime)
|
|
{
|
|
timedValue = cg_beatingSiegeTime;
|
|
}
|
|
else
|
|
{
|
|
timedValue = team2Timed;
|
|
}
|
|
}
|
|
|
|
if (timedTeam != TEAM_FREE)
|
|
{ //one of the teams has a timer
|
|
int timeRemaining;
|
|
qboolean isMyTeam = qfalse;
|
|
|
|
if (cgs.siegeTeamSwitch && !cg_beatingSiegeTime)
|
|
{ //in switchy mode but not beating a time, so count up.
|
|
timeRemaining = (cg.time-cgSiegeRoundBeganTime);
|
|
if (timeRemaining < 0)
|
|
{
|
|
timeRemaining = 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
timeRemaining = (((cgSiegeRoundBeganTime)+timedValue) - cg.time);
|
|
}
|
|
|
|
if (timeRemaining > timedValue)
|
|
{
|
|
timeRemaining = timedValue;
|
|
}
|
|
else if (timeRemaining < 0)
|
|
{
|
|
timeRemaining = 0;
|
|
}
|
|
|
|
if (timeRemaining)
|
|
{
|
|
timeRemaining /= 1000;
|
|
}
|
|
|
|
if (cg.predictedPlayerState.persistant[PERS_TEAM] == timedTeam)
|
|
{ //the team that's timed is the one this client is on
|
|
isMyTeam = qtrue;
|
|
}
|
|
|
|
CG_DrawSiegeTimer(timeRemaining, isMyTeam);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
cgSiegeEntityRender = 0;
|
|
}
|
|
|
|
if ( cg_siegeDeathTime )
|
|
{
|
|
int timeRemaining = ( cg_siegeDeathTime - cg.time );
|
|
|
|
if ( timeRemaining < 0 )
|
|
{
|
|
timeRemaining = 0;
|
|
cg_siegeDeathTime = 0;
|
|
}
|
|
|
|
if ( timeRemaining )
|
|
{
|
|
timeRemaining /= 1000;
|
|
}
|
|
|
|
CG_DrawSiegeDeathTimer( timeRemaining );
|
|
}
|
|
|
|
// don't draw center string if scoreboard is up
|
|
cg.scoreBoardShowing = CG_DrawScoreboard();
|
|
if ( !cg.scoreBoardShowing) {
|
|
CG_DrawCenterString();
|
|
}
|
|
|
|
// always draw chat
|
|
CG_ChatBox_DrawStrings();
|
|
}
|
|
|
|
|
|
static void CG_DrawTourneyScoreboard() {
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawActive
|
|
|
|
Perform all drawing needed to completely fill the screen
|
|
=====================
|
|
*/
|
|
void CG_DrawActive( stereoFrame_t stereoView ) {
|
|
float separation;
|
|
vec3_t baseOrg;
|
|
|
|
// optionally draw the info screen instead
|
|
if ( !cg.snap ) {
|
|
CG_DrawInformation();
|
|
return;
|
|
}
|
|
|
|
// optionally draw the tournement scoreboard instead
|
|
if ( cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR &&
|
|
( cg.snap->ps.pm_flags & PMF_SCOREBOARD ) ) {
|
|
CG_DrawTourneyScoreboard();
|
|
return;
|
|
}
|
|
|
|
switch ( stereoView ) {
|
|
case STEREO_CENTER:
|
|
separation = 0;
|
|
break;
|
|
case STEREO_LEFT:
|
|
separation = -cg_stereoSeparation.value / 2;
|
|
break;
|
|
case STEREO_RIGHT:
|
|
separation = cg_stereoSeparation.value / 2;
|
|
break;
|
|
default:
|
|
separation = 0;
|
|
CG_Error( "CG_DrawActive: Undefined stereoView" );
|
|
}
|
|
|
|
|
|
// clear around the rendered view if sized down
|
|
CG_TileClear();
|
|
|
|
// offset vieworg appropriately if we're doing stereo separation
|
|
VectorCopy( cg.refdef.vieworg, baseOrg );
|
|
if ( separation != 0 ) {
|
|
VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg );
|
|
}
|
|
|
|
cg.refdef.rdflags |= RDF_DRAWSKYBOX;
|
|
|
|
// 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();
|
|
}
|
|
|
|
|
|
|