1988 lines
43 KiB
C
1988 lines
43 KiB
C
// Copyright (C) 2001-2002 Raven Software
|
|
//
|
|
// cg_draw.c -- draw all of the graphical elements during
|
|
// active (after loading) gameplay
|
|
|
|
#include "cg_local.h"
|
|
|
|
#include "../ui/ui_shared.h"
|
|
|
|
// used for scoreboard
|
|
extern displayContextDef_t cgDC;
|
|
|
|
void CG_DrawRadar ( void );
|
|
void CG_DrawAutomap ( void );
|
|
void CG_DrawTimers ( void );
|
|
|
|
/*
|
|
================
|
|
CG_Draw3DModel
|
|
================
|
|
*/
|
|
void CG_Draw3DModel( float x, float y, float w, float h, qhandle_t model, qhandle_t skin, vec3_t origin, vec3_t angles )
|
|
{
|
|
refdef_t refdef;
|
|
refEntity_t ent;
|
|
|
|
CG_AdjustFrom640( &x, &y, &w, &h );
|
|
|
|
memset( &refdef, 0, sizeof( refdef ) );
|
|
|
|
memset( &ent, 0, sizeof( ent ) );
|
|
AnglesToAxis( angles, ent.axis );
|
|
VectorCopy( origin, ent.origin );
|
|
ent.hModel = model;
|
|
ent.customSkin = skin;
|
|
ent.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 );
|
|
}
|
|
|
|
void CG_Draw3DG2Model( float x, float y, float w, float h, void *ghoul2, qhandle_t skin, vec3_t origin, vec3_t angles )
|
|
{
|
|
refdef_t refdef;
|
|
refEntity_t ent;
|
|
|
|
CG_AdjustFrom640( &x, &y, &w, &h );
|
|
|
|
memset( &refdef, 0, sizeof( refdef ) );
|
|
|
|
memset( &ent, 0, sizeof( ent ) );
|
|
AnglesToAxis( angles, ent.axis );
|
|
VectorCopy( origin, ent.origin );
|
|
ent.ghoul2 = ghoul2;
|
|
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_DrawSnapshot
|
|
==================
|
|
*/
|
|
static float CG_DrawSnapshot( float y )
|
|
{
|
|
char *s;
|
|
int w;
|
|
|
|
s = va( "time:%i snap:%i cmd:%i", cg.snap->serverTime,
|
|
cg.latestSnapshotNum, cgs.serverCommandSequence );
|
|
|
|
w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 );
|
|
|
|
CG_DrawText ( 635 - w, y - 6, cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 );
|
|
|
|
return 20;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_DrawFPS
|
|
==================
|
|
*/
|
|
#define FPS_FRAMES 4
|
|
static float CG_DrawFPS( float y )
|
|
{
|
|
char *s;
|
|
int w;
|
|
static int previousTimes[FPS_FRAMES];
|
|
static int index;
|
|
int i, total;
|
|
int fps;
|
|
static int previous;
|
|
int t, frameTime;
|
|
|
|
// don't use serverTime, because that will be drifting to
|
|
// correct for internet lag changes, timescales, timedemos, etc
|
|
t = trap_Milliseconds();
|
|
frameTime = t - previous;
|
|
previous = t;
|
|
|
|
previousTimes[index % FPS_FRAMES] = frameTime;
|
|
index++;
|
|
|
|
if ( index > FPS_FRAMES )
|
|
{
|
|
// average multiple frames together to smooth changes out a bit
|
|
total = 0;
|
|
for ( i = 0 ; i < FPS_FRAMES ; i++ ) {
|
|
total += previousTimes[i];
|
|
}
|
|
if ( !total ) {
|
|
total = 1;
|
|
}
|
|
fps = 1000 * FPS_FRAMES / total;
|
|
|
|
s = va( "%i fps", fps );
|
|
|
|
w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 );
|
|
|
|
CG_DrawText ( 635 - w, y, cgs.media.hudFont, 0.53f, g_color_table[ColorIndex(COLOR_GREEN)], s, 0,0 );
|
|
}
|
|
|
|
return y + 20;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawRadar
|
|
=====================
|
|
*/
|
|
#define RADAR_RANGE 2500
|
|
#define RADAR_RADIUS 60
|
|
#define RADAR_X (580 - RADAR_RADIUS)
|
|
#define RADAR_Y 10
|
|
#define RADAR_CHAT_DURATION 6000
|
|
|
|
void CG_DrawRadar ( void )
|
|
{
|
|
vec4_t color;
|
|
vec4_t teamColor;
|
|
float arrow_w;
|
|
float arrow_h;
|
|
clientInfo_t *cl;
|
|
clientInfo_t *local;
|
|
int i;
|
|
float scale;
|
|
|
|
// Make sure the radar should be showing
|
|
if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 || cg.weaponMenuUp )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( (cg.predictedPlayerState.pm_flags & PMF_GHOST) || cgs.clientinfo[cg.predictedPlayerState.clientNum].team == TEAM_SPECTATOR )
|
|
{
|
|
return;
|
|
}
|
|
|
|
local = &cgs.clientinfo[ cg.snap->ps.clientNum ];
|
|
if ( !local->infoValid )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 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, RADAR_Y, RADAR_RADIUS*2, RADAR_RADIUS*2, cgs.media.radarShader );
|
|
|
|
arrow_w = 16 * RADAR_RADIUS / 128;
|
|
arrow_h = 16 * RADAR_RADIUS / 128;
|
|
|
|
// determine the color of the arrows to draw
|
|
switch(local->team)
|
|
{
|
|
default:
|
|
case TEAM_RED:
|
|
VectorCopy ( g_color_table[ColorIndex(COLOR_RED)], teamColor );
|
|
break;
|
|
|
|
case TEAM_BLUE:
|
|
VectorCopy ( g_color_table[ColorIndex(COLOR_BLUE)], teamColor );
|
|
break;
|
|
}
|
|
|
|
// Darken the color a tad
|
|
VectorScale ( teamColor, 0.5f, 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;
|
|
centity_t* cent;
|
|
|
|
cent = cg.radarEntities[i];
|
|
|
|
// Get the distances first
|
|
VectorSubtract ( cg.predictedPlayerState.origin, cent->lerpOrigin, dirPlayer );
|
|
dirPlayer[2] = 0;
|
|
distance = VectorNormalize ( dirPlayer );
|
|
|
|
if ( distance > RADAR_RANGE * 0.8f)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
distance = distance / RADAR_RANGE;
|
|
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 )
|
|
{
|
|
case ET_ITEM:
|
|
if ( cg_items[cent->currentState.modelindex].registered )
|
|
{
|
|
float x;
|
|
float y;
|
|
|
|
x = (float)RADAR_X + (float)RADAR_RADIUS + (float)sin (angle) * distance;
|
|
y = (float)RADAR_Y + (float)RADAR_RADIUS + (float)cos (angle) * distance;
|
|
|
|
trap_R_SetColor ( NULL );
|
|
CG_DrawPic ( x - 4, y - 4, 9, 9, cg_items[cent->currentState.modelindex].icon );
|
|
}
|
|
break;
|
|
|
|
case ET_PLAYER:
|
|
{
|
|
vec4_t color;
|
|
|
|
cl = &cgs.clientinfo[ cent->currentState.number ];
|
|
|
|
// not valid then dont draw it
|
|
if ( !cl->infoValid )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if ( cent->currentState.gametypeitems )
|
|
{
|
|
VectorCopy4 ( g_color_table[ColorIndex(COLOR_YELLOW)], color );
|
|
}
|
|
else
|
|
{
|
|
VectorCopy4 ( teamColor, color );
|
|
}
|
|
|
|
if (cl->mLastChatTime+RADAR_CHAT_DURATION > cg.time)
|
|
{
|
|
vec3_t finalColor;
|
|
|
|
scale = ((cg.time - cl->mLastChatTime) / (float)RADAR_CHAT_DURATION);
|
|
scale *= scale;
|
|
|
|
finalColor[0] = (color[0] * (scale)) + (colorWhite[0] * (1.0-scale));
|
|
finalColor[1] = (color[1] * (scale)) + (colorWhite[1] * (1.0-scale));
|
|
finalColor[2] = (color[2] * (scale)) + (colorWhite[2] * (1.0-scale));
|
|
finalColor[3] = color[3];
|
|
trap_R_SetColor ( finalColor );
|
|
scale += 1.0;
|
|
}
|
|
else
|
|
{
|
|
trap_R_SetColor ( color );
|
|
scale = 1.0;
|
|
}
|
|
|
|
CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS + sin (angle) * distance,
|
|
RADAR_Y + RADAR_RADIUS + cos (angle) * distance,
|
|
arrow_w, arrow_h,
|
|
(360 - cent->lerpAngles[YAW]) + cg.predictedPlayerState.viewangles[YAW], cgs.media.mAutomapPlayerIcon );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
trap_R_SetColor ( colorWhite );
|
|
CG_DrawRotatePic2( RADAR_X + RADAR_RADIUS, RADAR_Y + RADAR_RADIUS, arrow_w, arrow_h,
|
|
0, cgs.media.mAutomapPlayerIcon );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawTeamScores
|
|
=====================
|
|
*/
|
|
static void CG_DrawTeamScores ( float y )
|
|
{
|
|
char scores[2][16];
|
|
float w;
|
|
const char* s;
|
|
vec4_t fade = {1,1,1,0.7f};
|
|
float x1;
|
|
float y1;
|
|
float x2;
|
|
float y2;
|
|
|
|
// Make sure the radar should be showing
|
|
if ( cg.weaponMenuUp )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cgs.clientinfo[cg.predictedPlayerState.clientNum].team == TEAM_SPECTATOR )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cgs.scores1 == SCORE_NOT_PRESENT )
|
|
{
|
|
Com_sprintf (scores[0], sizeof(scores[0]), "-");
|
|
}
|
|
else
|
|
{
|
|
Com_sprintf (scores[0], sizeof(scores[0]), "%i", cgs.scores1);
|
|
}
|
|
|
|
if ( cgs.scores2 == SCORE_NOT_PRESENT )
|
|
{
|
|
Com_sprintf (scores[1], sizeof(scores[1]), "-");
|
|
}
|
|
else
|
|
{
|
|
Com_sprintf (scores[1], sizeof(scores[1]), "%i", cgs.scores2);
|
|
}
|
|
|
|
if ( cg_drawTeamScores.integer > 0 && cg_drawTeamScores.integer < 5 )
|
|
{
|
|
switch ( cg_drawTeamScores.integer )
|
|
{
|
|
default:
|
|
case 3:
|
|
x1 = 438;
|
|
x2 = 400;
|
|
y1 = 5;
|
|
y2 = 5;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
x1 = 430;
|
|
y1 = 425;
|
|
x2 = 470;
|
|
y2 = 425;
|
|
break;
|
|
|
|
case 2:
|
|
x1 = 340;
|
|
y1 = 395;
|
|
x2 = 380;
|
|
y2 = 395;
|
|
break;
|
|
|
|
case 4:
|
|
x1 = 600;
|
|
x2 = 600;
|
|
y1 = 200;
|
|
y2 = 250;
|
|
break;
|
|
}
|
|
|
|
trap_R_SetColor ( fade );
|
|
CG_DrawPic ( x1, y1, 32, 32, cgs.media.redFriendShader );
|
|
CG_DrawPic ( x2, y2, 32, 32, cgs.media.blueFriendShader );
|
|
|
|
w = trap_R_GetTextWidth ( scores[0], cgs.media.hudFont, 0.5f, 0 );
|
|
CG_DrawText ( x1 + 16 - w / 2, y1 + 6, cgs.media.hudFont, 0.5f, colorWhite, scores[0], 0, DT_OUTLINE );
|
|
|
|
w = trap_R_GetTextWidth ( scores[1], cgs.media.hudFont, 0.5f, 0 );
|
|
CG_DrawText ( x2 + 16 - w / 2, y2 + 6, cgs.media.hudFont, 0.45f, colorWhite, scores[1], 0, DT_OUTLINE );
|
|
|
|
if ( cgs.gametypeData->respawnType == RT_NONE )
|
|
{
|
|
s = va("%d/%d", cg.predictedPlayerState.persistant[PERS_RED_ALIVE_COUNT], CG_TeamCount(TEAM_RED) );
|
|
w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.35f, 0 );
|
|
CG_DrawText ( x1 + 16 - w / 2, y1 + 24, cgs.media.hudFont, 0.35f, colorMdGrey, s, 0, DT_OUTLINE );
|
|
|
|
s = va("%d/%d", cg.predictedPlayerState.persistant[PERS_BLUE_ALIVE_COUNT], CG_TeamCount(TEAM_BLUE) );
|
|
w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.35f, 0 );
|
|
CG_DrawText ( x2 + 16 - w / 2, y2 + 24, cgs.media.hudFont, 0.35f, colorMdGrey, s, 0, DT_OUTLINE );
|
|
}
|
|
|
|
trap_R_SetColor ( NULL );
|
|
}
|
|
else
|
|
{
|
|
s = va ( "Red: %s Blue: %s", scores[0], scores[1] );
|
|
w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.35f, 0 );
|
|
CG_DrawText ( RADAR_X + RADAR_RADIUS - w / 2, y, cgs.media.hudFont, 0.35f, g_color_table[ColorIndex(COLOR_GREEN)], s, 0, DT_OUTLINE );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawUpperRight
|
|
=====================
|
|
*/
|
|
static void CG_DrawUpperRight( void )
|
|
{
|
|
float y;
|
|
|
|
y = 0;
|
|
|
|
if ( cg.scoreBoardShowing )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg_drawSnapshot.integer )
|
|
{
|
|
y = CG_DrawSnapshot( y );
|
|
}
|
|
|
|
switch ( cg_drawRadar.integer )
|
|
{
|
|
// Off unless the key is pressed
|
|
case 0:
|
|
if ( cg.showAutomap )
|
|
{
|
|
// If in rmg default to the auto map and if in a non team game
|
|
// the radar is useless so default to automap as well.
|
|
if ( cg.mInRMG || !cgs.gametypeData->teams )
|
|
{
|
|
CG_DrawAutomap ( );
|
|
}
|
|
else
|
|
{
|
|
CG_DrawRadar ( );
|
|
}
|
|
}
|
|
break;
|
|
|
|
// Draw the radar unless the automap key is down
|
|
case 1:
|
|
if ( cg.showAutomap && cg.mInRMG )
|
|
{
|
|
CG_DrawAutomap ( );
|
|
}
|
|
// If its a team game allow radar
|
|
else if ( cgs.gametypeData->teams )
|
|
{
|
|
CG_DrawRadar ( );
|
|
}
|
|
break;
|
|
|
|
// Draw the automap only, but if the key is pressed show the radar
|
|
case 2:
|
|
if ( cgs.gametypeData->teams && (cg.showAutomap || !cg.mInRMG) )
|
|
{
|
|
CG_DrawRadar ( );
|
|
}
|
|
else
|
|
{
|
|
CG_DrawAutomap ( );
|
|
}
|
|
break;
|
|
}
|
|
|
|
if ( cg_drawFPS.integer )
|
|
{
|
|
y = CG_DrawFPS( y );
|
|
y = 18;
|
|
}
|
|
else
|
|
{
|
|
y = 10;
|
|
}
|
|
|
|
if ( cg_drawTeamScores.integer && cgs.gametypeData->teams )
|
|
{
|
|
CG_DrawTeamScores ( y );
|
|
}
|
|
|
|
CG_DrawTimers ( );
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
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
|
|
|
|
Draws a little connection icon on the screen when connection to the
|
|
server is lost
|
|
==============
|
|
*/
|
|
static void CG_DrawDisconnect ( void )
|
|
{
|
|
float x;
|
|
float y;
|
|
int cmdNum;
|
|
usercmd_t cmd;
|
|
|
|
// draw the phone jack if we are completely past our buffers
|
|
cmdNum = trap_GetCurrentCmdNumber() - CMD_BACKUP + 1;
|
|
trap_GetUserCmd( cmdNum, &cmd );
|
|
|
|
// special check for map_restart
|
|
if ( cmd.serverTime <= cg.snap->ps.commandTime || cmd.serverTime > cg.time )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// blink the icon
|
|
if ( ( cg.time >> 9 ) & 1 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
x = 640 - 76;
|
|
y = 480 - 165;
|
|
|
|
CG_DrawPic( x - 3, y - 3, 56, 56, cgs.media.disconnectShader );
|
|
}
|
|
|
|
|
|
static vec4_t ChatColor =
|
|
{ // green
|
|
0.0, 1.0, 0.0, 1.0
|
|
};
|
|
|
|
/*
|
|
==============
|
|
CG_DrawAutomap
|
|
|
|
Draws the rmg map with any radar items placed on it.
|
|
==============
|
|
*/
|
|
void CG_DrawAutomap ( void )
|
|
{
|
|
TCGConvertPos *pos = (TCGConvertPos *)cg.sharedBuffer;
|
|
clientInfo_t *cl, *local;
|
|
int i;
|
|
qhandle_t teamIcon;
|
|
vec4_t teamColor, finalColor;
|
|
int arrow_w;
|
|
int arrow_h;
|
|
int item_w;
|
|
int item_h;
|
|
float scale;
|
|
|
|
// Only enabled in the RMG
|
|
if (!cg.mInRMG || cg.weaponMenuUp )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// invalid parms, so don't draw!
|
|
if (cg_automap_x.integer < 0 || cg_automap_x.integer > 640-32 ||
|
|
cg_automap_y.integer < 0 || cg_automap_y.integer > 480-32 ||
|
|
cg_automap_w.integer < 32 || cg_automap_w.integer > 640 ||
|
|
cg_automap_h.integer < 32 || cg_automap_h.integer > 480 ||
|
|
cg_automap_x.integer + cg_automap_w.integer > 640 ||
|
|
cg_automap_y.integer + cg_automap_h.integer > 480)
|
|
{
|
|
Com_Printf("Automap drawing coordinates out of range!\n");
|
|
return;
|
|
}
|
|
|
|
// Register the automap image if not already registerd
|
|
if ( !cgs.media.mAutomap )
|
|
{
|
|
trap_CM_TM_Upload(0, 0);
|
|
cgs.media.mAutomap = trap_R_RegisterShader( "gfx/menus/rmg/automap" );
|
|
}
|
|
|
|
finalColor[0] = finalColor[1] = finalColor[2] = 1.0;
|
|
finalColor[3] = cg_automap_a.value;
|
|
if (finalColor[3] > 1.0)
|
|
{
|
|
finalColor[3] = 1.0;
|
|
}
|
|
|
|
if ( cgs.media.mAutomap)
|
|
{
|
|
trap_R_SetColor (finalColor);
|
|
CG_DrawPic( cg_automap_x.integer, cg_automap_y.integer, cg_automap_w.integer, cg_automap_h.integer, cgs.media.mAutomap );
|
|
}
|
|
|
|
local = &cgs.clientinfo[ cg.snap->ps.clientNum ];
|
|
if ( !local->infoValid)
|
|
{
|
|
return;
|
|
}
|
|
|
|
arrow_w = 16 * cg_automap_w.integer / 512;
|
|
arrow_h = 16 * cg_automap_h.integer / 512;
|
|
item_w = 24 * cg_automap_w.integer / 512;
|
|
item_h = 24 * cg_automap_h.integer / 512;
|
|
|
|
switch(local->team)
|
|
{
|
|
case TEAM_RED:
|
|
teamColor[0] = 1.0;
|
|
teamColor[1] = 0.0;
|
|
teamColor[2] = 0.0;
|
|
break;
|
|
case TEAM_BLUE:
|
|
teamColor[0] = 0.0;
|
|
teamColor[1] = 0.0;
|
|
teamColor[2] = 1.0;
|
|
break;
|
|
case TEAM_SPECTATOR:
|
|
default:
|
|
teamColor[0] = 1.0;
|
|
teamColor[1] = 1.0;
|
|
teamColor[2] = 1.0;
|
|
break;
|
|
}
|
|
teamIcon = cgs.media.mAutomapPlayerIcon;
|
|
teamColor[3] = cg_automap_a.value + 0.1;
|
|
if (teamColor[3] > 1.0)
|
|
{
|
|
teamColor[3] = 1.0;
|
|
}
|
|
|
|
// Only team games show other people on the automap
|
|
if ( cgs.gametypeData->teams )
|
|
{
|
|
// Draw all of the radar entities. Draw them backwards so players are drawn last
|
|
for ( i = cg.radarEntityCount -1 ; i >= 0 ; i-- )
|
|
{
|
|
centity_t* cent;
|
|
|
|
cent = cg.radarEntities[i];
|
|
|
|
switch ( cent->currentState.eType )
|
|
{
|
|
case ET_ITEM:
|
|
if ( cg_items[cent->currentState.modelindex].registered )
|
|
{
|
|
VectorCopy( cent->lerpOrigin, pos->mOrigin);
|
|
pos->mWidth = cg_automap_w.integer;
|
|
pos->mHeight = cg_automap_h.integer;
|
|
trap_CM_TM_ConvertPosition();
|
|
|
|
trap_R_SetColor ( NULL );
|
|
CG_DrawPic ( pos->mX - item_w / 2 + cg_automap_x.integer,
|
|
pos->mY - item_h / 2 + cg_automap_y.integer,
|
|
item_w, item_h, cg_items[cent->currentState.modelindex].icon );
|
|
}
|
|
break;
|
|
|
|
case ET_PLAYER:
|
|
|
|
cl = &cgs.clientinfo[ cent->currentState.number ];
|
|
|
|
// not valid then dont draw it
|
|
if ( !cl->infoValid )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (cl->mLastChatTime+RADAR_CHAT_DURATION > cg.time)
|
|
{
|
|
vec3_t finalColor;
|
|
|
|
scale = ((cg.time - cl->mLastChatTime) / (float)RADAR_CHAT_DURATION);
|
|
scale *= scale;
|
|
|
|
finalColor[0] = (teamColor[0] * (scale)) + (colorWhite[0] * (1.0-scale));
|
|
finalColor[1] = (teamColor[1] * (scale)) + (colorWhite[1] * (1.0-scale));
|
|
finalColor[2] = (teamColor[2] * (scale)) + (colorWhite[2] * (1.0-scale));
|
|
finalColor[3] = teamColor[3];
|
|
trap_R_SetColor ( finalColor );
|
|
scale += 1.0;
|
|
}
|
|
else
|
|
{
|
|
trap_R_SetColor ( teamColor );
|
|
scale = 1.0;
|
|
}
|
|
|
|
VectorCopy( cent->lerpOrigin, pos->mOrigin);
|
|
pos->mWidth = cg_automap_w.integer;
|
|
pos->mHeight = cg_automap_h.integer;
|
|
trap_CM_TM_ConvertPosition();
|
|
|
|
CG_DrawRotatePic2( pos->mX + cg_automap_x.integer,
|
|
pos->mY + cg_automap_y.integer,
|
|
arrow_w*scale, arrow_h*scale,
|
|
(360 - cent->lerpAngles[1]) - 90.0, teamIcon );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
VectorCopy(cg.refdef.vieworg, pos->mOrigin);
|
|
pos->mWidth = cg_automap_w.integer;
|
|
pos->mHeight = cg_automap_h.integer;
|
|
trap_CM_TM_ConvertPosition();
|
|
|
|
teamColor[0] = teamColor[1] = teamColor[2] = 1.0;
|
|
teamColor[3] = cg_automap_a.value + 0.2;
|
|
if (teamColor[3] > 1.0)
|
|
{
|
|
teamColor[3] = 1.0;
|
|
}
|
|
trap_R_SetColor ( teamColor );
|
|
scale = 1.0;
|
|
|
|
CG_DrawRotatePic2( pos->mX + cg_automap_x.integer, pos->mY + cg_automap_y.integer, arrow_w*scale, arrow_h*scale,
|
|
(360 - cg.refdef.viewangles[1]) - 90.0, cgs.media.mAutomapPlayerIcon );
|
|
}
|
|
|
|
#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;
|
|
|
|
trap_R_SetColor( NULL );
|
|
|
|
if ( !cg_lagometer.integer || cgs.localServer )
|
|
{
|
|
CG_DrawDisconnect();
|
|
return;
|
|
}
|
|
|
|
// draw the graph
|
|
x = 640 - 76;
|
|
y = 480 - 165;
|
|
|
|
CG_DrawPic( x - 3, y - 3, 56, 56, cgs.media.lagometerShader );
|
|
|
|
ax = x;
|
|
ay = y;
|
|
aw = 48;
|
|
ah = 48;
|
|
CG_AdjustFrom640( &ax, &ay, &aw, &ah );
|
|
|
|
color = -1;
|
|
range = ah / 3;
|
|
mid = ay + range;
|
|
|
|
vscale = range / MAX_LAGOMETER_RANGE;
|
|
|
|
// draw the frame interpoalte / extrapolate graph
|
|
for ( a = 0 ; a < aw ; a++ ) {
|
|
i = ( lagometer.frameCount - 1 - a ) & (LAG_SAMPLES - 1);
|
|
v = lagometer.frameSamples[i];
|
|
v *= vscale;
|
|
if ( v > 0 ) {
|
|
if ( color != 1 ) {
|
|
color = 1;
|
|
trap_R_SetColor( g_color_table[ColorIndex(COLOR_YELLOW)] );
|
|
}
|
|
if ( v > range ) {
|
|
v = range;
|
|
}
|
|
trap_R_DrawStretchPic ( ax + aw - a, mid - v, 1, v, 0, 0, 0, 0, NULL, 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, NULL, 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, NULL, 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, NULL, cgs.media.whiteShader );
|
|
}
|
|
}
|
|
|
|
trap_R_SetColor( NULL );
|
|
|
|
CG_DrawDisconnect();
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
CENTER PRINTING
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
|
|
/*
|
|
==============
|
|
CG_CenterPrint
|
|
|
|
Called for important messages that should stay in the center of the screen
|
|
for a few moments
|
|
==============
|
|
*/
|
|
void CG_CenterPrint( const char *str, float scale )
|
|
{
|
|
char *s;
|
|
|
|
Q_strncpyz( cg.centerPrint, str, sizeof(cg.centerPrint) );
|
|
|
|
cg.centerPrintTime = cg.time;
|
|
cg.centerPrintScale = scale;
|
|
|
|
// 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;
|
|
|
|
if ( !cg_centertime.value )
|
|
{
|
|
return;
|
|
}
|
|
|
|
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_centerY.integer;
|
|
|
|
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 = trap_R_GetTextWidth(linebuffer, cgs.media.hudFont, cg.centerPrintScale, 0 );
|
|
h = trap_R_GetTextHeight(linebuffer, cgs.media.hudFont, cg.centerPrintScale, 0 );
|
|
x = (SCREEN_WIDTH - w) / 2;
|
|
CG_DrawText (x, y + h, cgs.media.hudFont, cg.centerPrintScale, color, linebuffer, 0, DT_OUTLINE );
|
|
y += h + 6;
|
|
|
|
while ( *start && ( *start != '\n' ) )
|
|
{
|
|
start++;
|
|
}
|
|
|
|
if ( !*start )
|
|
{
|
|
break;
|
|
}
|
|
start++;
|
|
}
|
|
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawCenterText
|
|
=================
|
|
*/
|
|
static void CG_DrawCenterText ( void )
|
|
{
|
|
int w;
|
|
int h;
|
|
|
|
if ( cgs.gametypeMessageTime < cg.time )
|
|
{
|
|
CG_DrawCenterString ( );
|
|
return;
|
|
}
|
|
|
|
w = trap_R_GetTextWidth( cgs.gametypeMessage, cgs.media.hudFont, 0.43f, 0 );
|
|
h = trap_R_GetTextHeight( cgs.gametypeMessage, cgs.media.hudFont, 0.43f, 0 );
|
|
CG_DrawText ( (SCREEN_WIDTH - w) / 2, cg_centerY.integer + h, cgs.media.hudFont, 0.43f, colorWhite, cgs.gametypeMessage, 0, DT_OUTLINE );
|
|
}
|
|
|
|
|
|
/*
|
|
=================
|
|
CG_DrawCrosshair
|
|
=================
|
|
*/
|
|
static void CG_DrawCrosshair(void)
|
|
{
|
|
float w;
|
|
float h;
|
|
qhandle_t hShader;
|
|
float x;
|
|
float y;
|
|
float scale;
|
|
int ca;
|
|
qboolean zoomed;
|
|
|
|
if ( !cg_drawCrosshair.integer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.predictedPlayerState.stats[STAT_USEWEAPONDROP] )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If zoomed or unzoomed with the sniper rifle dont draw the standard crosshair
|
|
zoomed = (cg.predictedPlayerState.pm_flags&PMF_ZOOMED);
|
|
if ( zoomed || (cg.predictedPlayerState.weapon==WP_MSG90A1 && !zoomed) )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.snap->ps.pm_type == PM_SPECTATOR )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.renderingThirdPerson )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Default crosshair color
|
|
trap_R_SetColor( cg.crosshairRGBA );
|
|
|
|
// Change the crosshair color when its on friendly targets
|
|
if ( cg.crosshairColorClientNum != -1 && cgs.gametypeData->teams )
|
|
{
|
|
// Is the crosshair over a friendly target?
|
|
if ( cgs.clientinfo[ cg.crosshairColorClientNum ].team == cgs.clientinfo[ cg.snap->ps.clientNum ].team )
|
|
{
|
|
trap_R_SetColor( cg.crosshairFriendRGBA );
|
|
}
|
|
}
|
|
|
|
w = h = cg_crosshairSize.value;
|
|
|
|
// Determine the
|
|
if ( cg_crosshairGrow.integer )
|
|
{
|
|
scale = ((float)cg.predictedPlayerState.inaccuracy / ((float)weaponData[cg.predictedPlayerState.weapon].attack[ATTACK_NORMAL].maxInaccuracy+1));
|
|
scale = 1 + scale * 1;
|
|
}
|
|
else
|
|
{
|
|
scale = 1;
|
|
}
|
|
|
|
if ( scale > 2 )
|
|
{
|
|
scale = 2;
|
|
}
|
|
|
|
w = w * scale;
|
|
h = h * scale;
|
|
|
|
{
|
|
x = cg_crosshairX.integer;
|
|
y = cg_crosshairY.integer;
|
|
}
|
|
|
|
CG_AdjustFrom640( &x, &y, &w, &h );
|
|
|
|
ca = cg_drawCrosshair.integer;
|
|
if (ca < 0)
|
|
{
|
|
ca = 0;
|
|
}
|
|
|
|
hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ];
|
|
|
|
trap_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (cg.refdef.width - w),
|
|
y + cg.refdef.y + 0.5 * (cg.refdef.height - h),
|
|
w, h, 0, 0, 1, 1, NULL, hShader );
|
|
}
|
|
|
|
qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y)
|
|
{
|
|
int xcenter, ycenter;
|
|
vec3_t local, transformed;
|
|
vec3_t vfwd;
|
|
vec3_t vright;
|
|
vec3_t vup;
|
|
float xzi;
|
|
float yzi;
|
|
|
|
// xcenter = cg.refdef.width / 2;//gives screen coords adjusted for resolution
|
|
// ycenter = cg.refdef.height / 2;//gives screen coords adjusted for resolution
|
|
|
|
//NOTE: did it this way because most draw functions expect virtual 640x480 coords
|
|
// and adjust them for current resolution
|
|
xcenter = 640 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn
|
|
ycenter = 480 / 2;//gives screen coords in virtual 640x480, to be adjusted when drawn
|
|
|
|
AngleVectors (cg.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.01)
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
xzi = xcenter / transformed[2] * (90.0/cg.refdef.fov_x);
|
|
yzi = ycenter / transformed[2] * (90.0/cg.refdef.fov_y);
|
|
|
|
*x = xcenter + xzi * transformed[0];
|
|
*y = ycenter - yzi * transformed[1];
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
qboolean CG_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y )
|
|
{
|
|
float xF, yF;
|
|
qboolean retVal = CG_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF );
|
|
*x = (int)xF;
|
|
*y = (int)yF;
|
|
return retVal;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_ScanForCrosshairEntity
|
|
=================
|
|
*/
|
|
static void CG_ScanForCrosshairEntity( void )
|
|
{
|
|
trace_t trace;
|
|
vec3_t start;
|
|
vec3_t end;
|
|
int content;
|
|
|
|
VectorCopy( cg.refdef.vieworg, start );
|
|
VectorMA( start, 131072, cg.refdef.viewaxis[0], end );
|
|
|
|
cg.crosshairColorClientNum = -1;
|
|
|
|
CG_Trace( &trace, start, vec3_origin, vec3_origin, end,
|
|
cg.snap->ps.clientNum, MASK_SHOT );
|
|
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;
|
|
}
|
|
|
|
// People playing a team game cant see the name of people not on their team, unless
|
|
// they are spectating.
|
|
if ( cgs.gametypeData->teams )
|
|
{
|
|
if ( cg.predictedPlayerState.pm_type != PM_SPECTATOR && !(cg.predictedPlayerState.pm_flags&PMF_GHOST) && !(cg.predictedPlayerState.pm_flags&PMF_FOLLOW) )
|
|
{
|
|
if ( cgs.clientinfo[ trace.entityNum ].team != cg.predictedPlayerState.persistant[PERS_TEAM] )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// update the fade timer
|
|
cg.crosshairClientNum = trace.entityNum;
|
|
cg.crosshairClientTime = cg.time;
|
|
cg.crosshairColorClientNum = trace.entityNum;
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawCrosshairNames
|
|
=====================
|
|
*/
|
|
static void CG_DrawCrosshairNames( void )
|
|
{
|
|
float *color;
|
|
char *name;
|
|
float w;
|
|
int y;
|
|
|
|
if ( !cg_drawCrosshair.integer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( !cg_drawCrosshairNames.integer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( cg.renderingThirdPerson )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// If the player isnt spectating then make sure he cant see enemies names
|
|
if ( cgs.gametypeData->teams )
|
|
{
|
|
if ( cg.predictedPlayerState.pm_type != PM_SPECTATOR && !(cg.predictedPlayerState.pm_flags&PMF_GHOST) && !(cg.predictedPlayerState.pm_flags&PMF_FOLLOW) )
|
|
{
|
|
if ( cgs.clientinfo[ cg.crosshairClientNum ].team != cg.predictedPlayerState.persistant[PERS_TEAM] )
|
|
{
|
|
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 ( cg_drawCrosshairNames.integer == 2 ) // Just below the crosshair
|
|
{
|
|
y = (SCREEN_HEIGHT / 2) + 20 ;
|
|
}
|
|
else
|
|
{
|
|
y = 465 ;
|
|
}
|
|
|
|
|
|
color[3] *= 0.5f;
|
|
w = trap_R_GetTextWidth(name, cgs.media.hudFont, 0.43f, 0);
|
|
CG_DrawText( 320 - w / 2, y, cgs.media.hudFont, 0.43f, color, name, 0, 0 );
|
|
|
|
trap_R_SetColor( NULL );
|
|
}
|
|
|
|
|
|
//==============================================================================
|
|
|
|
/*
|
|
=================
|
|
CG_DrawSpectator
|
|
=================
|
|
*/
|
|
static void CG_DrawSpectator(void)
|
|
{
|
|
const char* s;
|
|
float y;
|
|
|
|
if ( cg.scoreBoardShowing )
|
|
{
|
|
return;
|
|
}
|
|
|
|
y = 415;
|
|
|
|
// Need to be a ghost or someone following who isnt a spectator to see the respawn time
|
|
if ( (cg.predictedPlayerState.pm_flags & PMF_GHOST) ||
|
|
((cg.snap->ps.pm_flags & PMF_FOLLOW) && cgs.clientinfo[cg.clientNum].team != TEAM_SPECTATOR) )
|
|
{
|
|
if ( cg.predictedPlayerState.respawnTimer )
|
|
{
|
|
int time;
|
|
time = cg.predictedPlayerState.respawnTimer - cg.time;
|
|
time /= 1000;
|
|
if ( time < 1 )
|
|
{
|
|
time = 1;
|
|
}
|
|
s = va("RESPAWN IN %i SECONDS", time );
|
|
}
|
|
else
|
|
{
|
|
s = "GHOST";
|
|
}
|
|
}
|
|
else
|
|
s = "SPECTATOR";
|
|
|
|
if ( (cg.snap->ps.pm_flags & PMF_FOLLOW) )
|
|
{
|
|
y = 65;
|
|
}
|
|
|
|
CG_DrawText ( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.45f, 0 ) / 2,
|
|
y, cgs.media.hudFont, 0.45f, colorWhite, s, 0, DT_OUTLINE );
|
|
|
|
// Draw some instructions on the screen for joining the game when the client is a spectator
|
|
if ( cgs.clientinfo[cg.clientNum].team == TEAM_SPECTATOR )
|
|
{
|
|
// Make sure they arent following someone and arent a ghost
|
|
if ( !(cg.predictedPlayerState.pm_flags & (PMF_GHOST|PMF_FOLLOW)) )
|
|
{
|
|
s = "press ESC and use the PLAYER menu to play";
|
|
CG_DrawText( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.43f, 0 ) / 2,
|
|
y + 15, cgs.media.hudFont, 0.43f, colorWhite, s, 0, DT_OUTLINE );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawVote
|
|
=================
|
|
*/
|
|
static void CG_DrawVote(void)
|
|
{
|
|
char *s;
|
|
int sec;
|
|
|
|
if ( !cgs.voteTime )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// play a talk beep whenever it is modified
|
|
if ( cgs.voteModified )
|
|
{
|
|
cgs.voteModified = qfalse;
|
|
trap_S_StartLocalSound( cgs.media.talkSound, CHAN_LOCAL_SOUND );
|
|
}
|
|
|
|
sec = ( cgs.voteDuration - ( cg.time - cgs.voteTime ) ) / 1000;
|
|
|
|
if ( sec < 0 )
|
|
{
|
|
sec = 0;
|
|
}
|
|
|
|
s = va("VOTE(%i):%s", sec, cgs.voteString );
|
|
CG_DrawText ( 10, 58, cgs.media.hudFont, 0.40f, colorLtGrey, s, 0, DT_OUTLINE );
|
|
|
|
s = va("needed:%i yes:%i no:%i", cgs.voteNeeded, cgs.voteYes, cgs.voteNo);
|
|
CG_DrawText ( 10, 70, cgs.media.hudFont, 0.40f, colorLtGrey, s, 0, DT_OUTLINE );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawIntermission
|
|
=================
|
|
*/
|
|
static void CG_DrawIntermission( void )
|
|
{
|
|
cg.scoreFadeTime = cg.time;
|
|
cg.scoreBoardShowing = CG_DrawScoreboard();
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawFollow
|
|
=================
|
|
*/
|
|
static qboolean CG_DrawFollow( void )
|
|
{
|
|
const char *s;
|
|
|
|
if ( cg.scoreBoardShowing )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
if ( !(cg.snap->ps.pm_flags & PMF_FOLLOW) )
|
|
{
|
|
return qfalse;
|
|
}
|
|
|
|
s = va("following %s", cgs.clientinfo[ cg.snap->ps.clientNum ].name );
|
|
CG_DrawText ( 320 - trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.63f, 0 ) / 2,
|
|
40, cgs.media.hudFont, 0.63f, colorWhite, s, 0, DT_OUTLINE );
|
|
|
|
CG_DrawSpectator ( );
|
|
|
|
return qtrue;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawWarmup
|
|
=================
|
|
*/
|
|
static void CG_DrawWarmup( void )
|
|
{
|
|
int w;
|
|
int sec;
|
|
const char *s;
|
|
|
|
sec = cg.warmup;
|
|
if ( !sec )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ( sec < 0 )
|
|
{
|
|
s = "Waiting for players";
|
|
|
|
w = trap_R_GetTextWidth ( s, cgs.media.hudFont, 0.53f, 0 );
|
|
CG_DrawText ( 320 - w / 2, 24, cgs.media.hudFont, 0.53f, colorWhite, s, 0, DT_OUTLINE );
|
|
|
|
cg.warmupCount = 0;
|
|
return;
|
|
}
|
|
|
|
sec = ( sec - cg.time ) / 1000;
|
|
if ( sec < 0 )
|
|
{
|
|
cg.warmup = 0;
|
|
sec = 0;
|
|
}
|
|
|
|
s = va( "map restart in: %i", sec + 1 );
|
|
if ( sec != cg.warmupCount )
|
|
{
|
|
cg.warmupCount = sec;
|
|
}
|
|
|
|
w = trap_R_GetTextWidth(s, cgs.media.hudFont, 0.53f, 0 );
|
|
CG_DrawText (320 - w / 2, 155, cgs.media.hudFont, 0.53f, colorWhite, s, 0, DT_OUTLINE );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawChat
|
|
=================
|
|
*/
|
|
static void CG_DrawChat ( void )
|
|
{
|
|
float w;
|
|
int h;
|
|
int i;
|
|
int chatHeight;
|
|
float y;
|
|
float x;
|
|
|
|
// Grab the users text height but dont let it get bigger than the define
|
|
chatHeight = cg_chatHeight.integer;
|
|
if (chatHeight > CHAT_HEIGHT )
|
|
{
|
|
chatHeight = CHAT_HEIGHT;
|
|
}
|
|
|
|
// Is the chat enabled right now?
|
|
if ( chatHeight <= 0 )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Nothing to draw
|
|
if (cgs.chatLastPos == cgs.chatPos )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Is it time to stop drawing the current chat message?
|
|
if ( cg.time - cgs.chatTime[cgs.chatLastPos % chatHeight] > cg_chatTime.integer)
|
|
{
|
|
cgs.chatLastPos++;
|
|
}
|
|
|
|
// Determine how tall the entire chat block is
|
|
h = (cgs.chatPos - cgs.chatLastPos) * 15;
|
|
|
|
if ( cg.scoreBoardShowing )
|
|
{
|
|
y = 395;
|
|
x = 50;
|
|
|
|
if ( cg.scoreBoardBottom + 10 > y - h )
|
|
{
|
|
y = cg.scoreBoardBottom + 10 + h;
|
|
}
|
|
|
|
if ( y > 480 )
|
|
{
|
|
y = 475;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
y = 380;
|
|
x = 35;
|
|
}
|
|
|
|
// Find the greatest width out of the strings rendered
|
|
for (w = 0, i = cgs.chatLastPos; i < cgs.chatPos; i++)
|
|
{
|
|
float tw = trap_R_GetTextWidth ( cgs.chatText[i % chatHeight], cgs.media.hudFont, 0.43f, 0 );
|
|
|
|
if (tw > w)
|
|
{
|
|
w = tw;
|
|
}
|
|
}
|
|
|
|
for (i = cgs.chatPos - 1; i >= cgs.chatLastPos ; i--)
|
|
{
|
|
qhandle_t font = cgs.media.hudFont;
|
|
float scale = 0.38f;
|
|
|
|
CG_DrawText ( x, y - (cgs.chatPos - i - 1) * 15,
|
|
font, scale, colorWhite, cgs.chatText[i % chatHeight], 0, DT_OUTLINE );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawMapChange
|
|
=================
|
|
*/
|
|
void CG_DrawMapChange ( void )
|
|
{
|
|
const char *s;
|
|
int w;
|
|
float x;
|
|
|
|
// Draw a nice background image
|
|
CG_DrawStretchPic ( 0, 0, 640, 480, 0, 0, 1, 1, colorWhite,
|
|
trap_R_RegisterShaderNoMip ( "gfx/menus/backdrop/pra1_sof2_logo" ) );
|
|
|
|
s = "Server Changing Maps";
|
|
w = trap_R_GetTextWidth(s, cgs.media.hudFont, 0.53f, 0 );
|
|
x = (SCREEN_WIDTH - w) / 2;
|
|
CG_DrawText (x, 360, cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 );
|
|
|
|
s = "Please wait";
|
|
w = trap_R_GetTextWidth(s, cgs.media.hudFont, 0.53f, 0 );
|
|
x = (SCREEN_WIDTH - w) / 2;
|
|
CG_DrawText (x, 400, cgs.media.hudFont, 0.53f, colorWhite, s, 0, 0 );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_DrawTimers
|
|
|
|
Draws the round, total, and frozer timers
|
|
==================
|
|
*/
|
|
void CG_DrawTimers ( void )
|
|
{
|
|
int y;
|
|
int x;
|
|
|
|
if ( !cg_drawTimer.integer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
y = 435;
|
|
x = 120;
|
|
|
|
if ( cg.predictedPlayerState.stats[STAT_FROZEN] )
|
|
{
|
|
CG_DrawTimer ( x, y, cgs.media.hudFont, 0.53f, colorGreen, DT_OUTLINE, -cg.predictedPlayerState.stats[STAT_FROZEN] );
|
|
}
|
|
else if ( cgs.gametypeTimerTime != 0 )
|
|
{
|
|
if ( cgs.gametypeTimerTime < cg.time )
|
|
{
|
|
return;
|
|
}
|
|
|
|
CG_DrawTimer ( x, y, cgs.media.hudFont, 0.53f, colorGreen, DT_OUTLINE, cgs.gametypeTimerTime - cg.time );
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
CG_DrawFlashBang
|
|
|
|
Renders and handles the progression of the flashgrenade. The flash grenade is just a white
|
|
overlay on the whole screen which fades out over time
|
|
==================
|
|
*/
|
|
static void CG_DrawFlashBang ( void )
|
|
{
|
|
vec4_t color;
|
|
|
|
// Is there an active flash bang?
|
|
if ( cg.flashbangTime + cg.flashbangFadeTime <= cg.time )
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Spectators and dead people dont need to see a flash so it can stop here
|
|
if ( (cg.predictedPlayerState.pm_flags & (PMF_GHOST|PMF_FOLLOW)) || cg.predictedPlayerState.pm_type != PM_NORMAL )
|
|
{
|
|
cg.flashbangTime = 0;
|
|
return;
|
|
}
|
|
|
|
VectorCopy ( colorWhite, color );
|
|
color[3] = cg.flashbangAlpha * (1.0f - ((float)(cg.time - cg.flashbangTime) / (float)cg.flashbangFadeTime));
|
|
|
|
color[3] *= 2.0;
|
|
if (color[3]<0)
|
|
{
|
|
color[3]=0;
|
|
}
|
|
|
|
if (color[3]>1)
|
|
{
|
|
color[3]=1;
|
|
}
|
|
|
|
CG_FillRect ( 0, 0, 640, 480, color );
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_DrawHUDIcons
|
|
|
|
draws the currnet list of hud icons in the bottom left corner
|
|
=================
|
|
*/
|
|
static void CG_DrawHUDIcons ( void )
|
|
{
|
|
int i;
|
|
float x;
|
|
|
|
// User turn off hud icons?
|
|
if ( !cg_drawHUDIcons.integer )
|
|
{
|
|
return;
|
|
}
|
|
|
|
x = 25;
|
|
|
|
for ( i = 0; i < MAX_HUDICONS; i ++ )
|
|
{
|
|
// No hud icon? skip it
|
|
if ( !cgs.hudIcons[i] )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
CG_DrawPic ( x, 425, 32, 32, cgs.gameIcons[ cgs.hudIcons[i] ] );
|
|
|
|
x += 40;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CG_Draw2D
|
|
=================
|
|
*/
|
|
static void CG_Draw2D( void )
|
|
{
|
|
static qboolean oldScoreBoardShowing = qfalse;
|
|
|
|
// if we are taking a levelshot for the menu, don't draw anything
|
|
if ( cg.levelShot )
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (cg.mMapChange)
|
|
{
|
|
CG_DrawMapChange ( );
|
|
return;
|
|
}
|
|
|
|
if ( cg_draw2D.integer == 0 )
|
|
{
|
|
CG_DrawFlashBang ( );
|
|
return;
|
|
}
|
|
|
|
// Handle the diabling of the console messages when in the scoreboard. enough
|
|
// clutter already on the scoreboard without seeing those too.
|
|
if ( oldScoreBoardShowing != cg.scoreBoardShowing )
|
|
{
|
|
trap_Cvar_Set ( "con_draw", cg.scoreBoardShowing?"0":"1" );
|
|
oldScoreBoardShowing = cg.scoreBoardShowing;
|
|
}
|
|
|
|
if ( cg.snap->ps.pm_type == PM_INTERMISSION )
|
|
{
|
|
CG_DrawIntermission();
|
|
CG_DrawChat ( );
|
|
return;
|
|
}
|
|
|
|
CG_DrawFlashBang ( );
|
|
|
|
// scan the known entities to see if the crosshair is sighted on one
|
|
CG_ScanForCrosshairEntity();
|
|
|
|
if ( cg.snap->ps.pm_type == PM_SPECTATOR || cg.snap->ps.persistant[PERS_TEAM] == TEAM_SPECTATOR )
|
|
{
|
|
CG_DrawSpectator();
|
|
CG_DrawCrosshair();
|
|
CG_DrawCrosshairNames();
|
|
CG_DrawHUDIcons();
|
|
}
|
|
else
|
|
{
|
|
// don't draw any status if dead or the scoreboard is being explicitly shown
|
|
if ( cg.snap->ps.stats[STAT_HEALTH] > 0 )
|
|
{
|
|
Menu_PaintAll();
|
|
|
|
if ( !cg.showScores )
|
|
{
|
|
CG_DrawHUDIcons();
|
|
CG_DrawTimedMenus();
|
|
CG_DrawCrosshair();
|
|
CG_DrawCrosshairNames();
|
|
}
|
|
}
|
|
}
|
|
|
|
CG_DrawVote();
|
|
|
|
CG_DrawLagometer();
|
|
|
|
if (!cg_paused.integer)
|
|
{
|
|
CG_DrawUpperRight();
|
|
}
|
|
|
|
// don't draw center string if scoreboard is up
|
|
cg.scoreBoardShowing = CG_DrawScoreboard();
|
|
if ( !cg.scoreBoardShowing)
|
|
{
|
|
CG_DrawCenterText();
|
|
if ( !CG_DrawFollow() )
|
|
{
|
|
CG_DrawWarmup();
|
|
}
|
|
}
|
|
|
|
// Always Draw chat
|
|
CG_DrawChat ( );
|
|
}
|
|
|
|
/*
|
|
=====================
|
|
CG_DrawActive
|
|
|
|
Perform all drawing needed to completely fill the screen
|
|
=====================
|
|
*/
|
|
void CG_DrawActive( stereoFrame_t stereoView )
|
|
{
|
|
float separation;
|
|
vec3_t baseOrg;
|
|
float parm1, parm2;
|
|
|
|
// optionally draw the info screen instead
|
|
if ( !cg.snap )
|
|
{
|
|
CG_DrawInformation();
|
|
return;
|
|
}
|
|
|
|
if (cg.mMapChange)
|
|
{
|
|
CG_DrawMapChange ( );
|
|
return;
|
|
}
|
|
|
|
// Handle the start of an inf match
|
|
if ( cg.predictedPlayerState.persistant[PERS_TEAM] != TEAM_SPECTATOR &&
|
|
!(cg.predictedPlayerState.pm_flags & PMF_GHOST) &&
|
|
!(cg.predictedPlayerState.pm_flags & PMF_FOLLOW) )
|
|
{
|
|
if ( cgs.gametypeTimerTime >= cg.time && !cg.predictedPlayerState.stats[STAT_FROZEN] && cgs.gametypeTimerTime != 0 && !cg.gametypeStarted )
|
|
{
|
|
CG_CenterPrint( "GO!", 1.1f );
|
|
cg.gametypeStarted = qtrue;
|
|
|
|
trap_S_StartLocalSound ( cgs.media.goSound, CHAN_AUTO );
|
|
}
|
|
}
|
|
|
|
// Popup the objectives scren if we need to
|
|
if( cg.popupObjectives && !cg.demoPlayback)
|
|
{
|
|
char temp[MAX_INFO_STRING];
|
|
char lastobjectives[MAX_INFO_STRING];
|
|
|
|
Com_sprintf ( lastobjectives, MAX_INFO_STRING, "%s_%s_%d", cgs.mapname, cgs.gametypeData->name, cgs.gameID );
|
|
trap_Cvar_VariableStringBuffer ( "cg_lastobjectives", temp, MAX_INFO_STRING );
|
|
|
|
if ( Q_stricmp ( temp, lastobjectives ) )
|
|
{
|
|
if ( !cgs.gametypeData->description )
|
|
{
|
|
// If the client isnt on a team yet and this is a team game, bring up the team dialog
|
|
if ( cgs.gametypeData->teams && cgs.clientinfo[cg.snap->ps.clientNum].team == TEAM_SPECTATOR )
|
|
{
|
|
trap_SendConsoleCommand ( "ui_team;" );
|
|
}
|
|
// Providing outfitting is available bring up the outfitting dialog
|
|
else if ( cgs.pickupsDisabled )
|
|
{
|
|
trap_SendConsoleCommand ( "ui_outfitting;" );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If already on a team then no need to choose a team
|
|
if ( cgs.clientinfo[cg.clientNum].team != TEAM_SPECTATOR )
|
|
{
|
|
trap_Cvar_Set ( "ui_info_seenobjectives", "1" );
|
|
}
|
|
|
|
trap_SendConsoleCommand ( "ui_objectives;" );
|
|
}
|
|
|
|
trap_Cvar_Set ( "cg_lastobjectives", lastobjectives );
|
|
}
|
|
|
|
cg.popupObjectives = qfalse;
|
|
}
|
|
|
|
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;
|
|
Com_Error( ERR_FATAL, "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 );
|
|
}
|
|
|
|
// When goggles are on there are a few passes to generate the effect
|
|
if ( cg.predictedPlayerState.pm_flags & PMF_GOGGLES_ON )
|
|
{
|
|
switch(cg.predictedPlayerState.stats[STAT_GOGGLES])
|
|
{
|
|
case GOGGLES_NIGHTVISION:
|
|
if (cg.mInRMG)
|
|
{
|
|
parm1 = RMG_distancecull.value;
|
|
parm2 = 0.0;
|
|
}
|
|
else
|
|
{
|
|
parm1 = 2500;
|
|
parm2 = 0.0;
|
|
}
|
|
break;
|
|
case GOGGLES_INFRARED:
|
|
parm1 = cgs.mIRSeeThrough;
|
|
parm2 = cgs.mIRDist;
|
|
break;
|
|
default:
|
|
parm1 = parm2 = 0.0;
|
|
break;
|
|
}
|
|
|
|
trap_R_DrawVisualOverlay ( cg.predictedPlayerState.stats[STAT_GOGGLES], qtrue, parm1, parm2);
|
|
|
|
// draw 3D view
|
|
trap_R_RenderScene( &cg.refdef );
|
|
|
|
trap_R_DrawVisualOverlay( cg.predictedPlayerState.stats[STAT_GOGGLES], qfalse, parm1, parm2);
|
|
}
|
|
// Normal rendering
|
|
else
|
|
{
|
|
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();
|
|
}
|
|
|