jkxr/Projects/Android/jni/OpenJK/codeJK2/cgame/cg_draw.cpp
2023-11-18 10:52:00 +00:00

3129 lines
80 KiB
C++

/*
===========================================================================
Copyright (C) 1999 - 2005, Id Software, Inc.
Copyright (C) 2000 - 2013, Raven Software, Inc.
Copyright (C) 2001 - 2013, Activision, Inc.
Copyright (C) 2013 - 2015, OpenJK contributors
This file is part of the OpenJK source code.
OpenJK is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License version 2 as
published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, see <http://www.gnu.org/licenses/>.
===========================================================================
*/
// cg_draw.c -- draw all of the graphical elements during
// active (after loading) gameplay
#include "../game/g_local.h"
#include "cg_local.h"
#include "cg_media.h"
#include "../game/objectives.h"
#include "bg_local.h"
#include <VrClientInfo.h>
#include "FxUtil.h"
void CG_DrawIconBackground(void);
void CG_DrawMoveSpeedIcon(void);
void CG_DrawMissionInformation( void );
void CG_DrawInventorySelect( void );
void CG_DrawForceSelect( void );
qboolean CG_WorldCoordToScreenCoord(vec3_t worldCoord, int *x, int *y);
qboolean CG_WorldCoordToScreenCoordFloat(vec3_t worldCoord, float *x, float *y);
extern float g_crosshairEntDist;
extern int g_crosshairSameEntTime;
extern int g_crosshairEntNum;
extern int g_crosshairEntTime;
qboolean cg_forceCrosshair = qfalse;
// bad cheating
extern int g_rocketLockEntNum;
extern int g_rocketLockTime;
extern int g_rocketSlackTime;
vec3_t vfwd;
vec3_t vright;
vec3_t vup;
vec3_t vfwd_n;
vec3_t vright_n;
vec3_t vup_n;
int infoStringCount;
//===============================================================
/*
================
CG_Draw3DModel
================
*/
static 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;
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.override_fov = true;
refdef.fov_x = 30;
refdef.fov_y = 30;
refdef.x = x;
refdef.y = y;
refdef.width = w;
refdef.height = h;
refdef.time = cg.time;
cgi_R_ClearScene();
cgi_R_AddRefEntityToScene( &ent );
cgi_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 speaker_i, vec3_t headAngles )
{
qhandle_t hm = 0;
qhandle_t hs = 0;
float len;
vec3_t origin;
vec3_t mins, maxs;
gentity_t *ent;
qboolean extensions = qfalse;
int talking = 0;
//If the talking ent is actually on the level, use his info
if ( cg.gameTextEntNum != -1 && cg.gameTextEntNum < ENTITYNUM_WORLD )
{
ent = &g_entities[cg.gameTextEntNum];
if ( ent && ent->client )
{
hm = ent->client->clientInfo.headModel;
if ( hm )
{
hs = ent->client->clientInfo.headSkin;
extensions = ent->client->clientInfo.extensions;
talking = gi.VoiceVolume[ent->s.number];
}
}
}
if ( !hm )
{
return;
}
if ( !talking )
{//no sound playing, don't display the head any more
cg.gameNextTextTime = cg.time;
return;
}
//add talking anim
if ( extensions && talking > 0 )
{
hs = hs + talking;
}
// offset the origin y and z to center the head
cgi_R_ModelBounds( hm, mins, maxs );
origin[2] = -0.5 * ( mins[2] + maxs[2] );
origin[1] = 0.5 * ( mins[1] + maxs[1] );
// calculate distance so the head nearly fills the box
// assume heads are taller than wide
len = 0.7 * ( maxs[2] - mins[2] );
origin[0] = len / 0.268; // len / tan( fov/2 )
CG_Draw3DModel( x, y, w, h, hm, hs, origin, headAngles );
}
/*
================
CG_DrawTalk
================
*/
static void CG_DrawTalk(centity_t *cent)
{
float size;
vec3_t angles;
// int totalLines,y,i;
vec4_t color;
if ( cg.gameNextTextTime > cg.time)
{
color[0] = colorTable[CT_BLACK][0];
color[1] = colorTable[CT_BLACK][1];
color[2] = colorTable[CT_BLACK][2];
color[3] = 0.350F;
cgi_R_SetColor(color); // Background
CG_DrawPic( 5, 27, 50, 64, cgs.media.ammoslider );
cgi_R_SetColor(colorTable[CT_LTPURPLE1]);
CG_DrawPic( 5, 6, 128, 64, cgs.media.talkingtop );
/*
totalLines = cg.scrollTextLines - cg.gameTextCurrentLine;
y = 6;
CG_DrawPic( 55, y, 16, 16, cgs.media.bracketlu );
CG_DrawPic( 616, y, 16, 16, cgs.media.bracketru );
for (i=1;i<totalLines;++i)
{
y +=16;
CG_DrawPic( 55, y, 16, 16, cgs.media.ammoslider );
CG_DrawPic( 616,y, 16, 16, cgs.media.ammoslider );
}
y +=16;
CG_DrawPic( 55, y, 16, 16, cgs.media.bracketld );
CG_DrawPic( 616,y, 16, 16, cgs.media.bracketrd );
*/
size = ICON_SIZE * 1.5;
VectorClear( angles );
angles[YAW] = 180;
CG_DrawHead( -6, 25, size, size, cg.gameTextSpeaker, angles );
cgi_R_SetColor(colorTable[CT_LTPURPLE1]); // Bottom
CG_DrawPic( 5, 90, 64, 16, cgs.media.talkingbot );
cgi_R_SetColor(NULL);
}
}
int cgi_UI_GetMenuInfo(char *menuFile,int *x,int *y);
/*
================
CG_DrawHUDRightFrame1
================
*/
static void CG_DrawHUDRightFrame1(int x,int y)
{
cgi_R_SetColor( colorTable[CT_WHITE] );
// Inner gray wire frame
CG_DrawPic( x, y, 80, 80, cgs.media.HUDInnerRight ); //
}
/*
================
CG_DrawHUDRightFrame2
================
*/
static void CG_DrawHUDRightFrame2(int x,int y)
{
cgi_R_SetColor( colorTable[CT_WHITE] );
CG_DrawPic( x, y, 80, 80, cgs.media.HUDRightFrame ); // Metal frame
}
/*
================
CG_DrawMessageLit
================
*/
static void CG_DrawMessageLit(centity_t *cent,int x,int y)
{
cgi_R_SetColor(colorTable[CT_WHITE]);
if (cg.missionInfoFlashTime > cg.time )
{
if (!((cg.time / 600 ) & 1))
{
if (!cg.messageLitActive)
{
cgi_S_StartSound( NULL, 0, CHAN_AUTO, cgs.media.messageLitSound );
cg.messageLitActive = qtrue;
}
cgi_R_SetColor(colorTable[CT_HUD_RED]);
CG_DrawPic( x + 33,y + 41, 16,16, cgs.media.messageLitOn);
}
else
{
cg.messageLitActive = qfalse;
}
}
cgi_R_SetColor(colorTable[CT_WHITE]);
CG_DrawPic( x + 33,y + 41, 16,16, cgs.media.messageLitOff);
}
/*
================
CG_DrawForcePower
================
*/
static void CG_DrawForcePower(centity_t *cent,int x,int y)
{
int i;
vec4_t calcColor;
float value,extra=0,inc,percent;
if ( !cent->gent->client->ps.forcePowersKnown )
{
return;
}
inc = (float) cent->gent->client->ps.forcePowerMax / MAX_TICS;
value = cent->gent->client->ps.forcePower;
if ( value > cent->gent->client->ps.forcePowerMax )
{//supercharged with force
extra = value - cent->gent->client->ps.forcePowerMax;
value = cent->gent->client->ps.forcePowerMax;
}
for (i=MAX_TICS-1;i>=0;i--)
{
if ( extra )
{//supercharged
memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t));
percent = 0.75f + (sin( cg.time * 0.005f )*((extra/cent->gent->client->ps.forcePowerMax)*0.25f));
calcColor[0] *= percent;
calcColor[1] *= percent;
calcColor[2] *= percent;
}
else if ( value <= 0 ) // partial tic
{
memcpy(calcColor, colorTable[CT_BLACK], sizeof(vec4_t));
}
else if (value < inc) // partial tic
{
memcpy(calcColor, colorTable[CT_LTGREY], sizeof(vec4_t));
percent = value / inc;
calcColor[0] *= percent;
calcColor[1] *= percent;
calcColor[2] *= percent;
}
else
{
memcpy(calcColor, colorTable[CT_LTGREY], sizeof(vec4_t));
}
cgi_R_SetColor( calcColor);
CG_DrawPic( x + forceTicPos[i].x,
y + forceTicPos[i].y,
forceTicPos[i].width,
forceTicPos[i].height,
forceTicPos[i].tic );
value -= inc;
}
}
/*
================
CG_DrawAmmo
================
*/
static void CG_DrawAmmo(centity_t *cent,int x,int y)
{
playerState_t *ps;
int numColor_i;
int i;
vec4_t calcColor;
float value,inc,percent;
ps = &cg.snap->ps;
if (!cent->currentState.weapon ) // We don't have a weapon right now
{
return;
}
if ( cent->currentState.weapon == WP_STUN_BATON )
{
return;
}
if ( cent->currentState.weapon == WP_SABER && cent->gent )
{
cgi_R_SetColor( colorTable[CT_WHITE] );
if ( !cg.saberAnimLevelPending && cent->gent->client )
{//uninitialized after a loadgame, cheat across and get it
cg.saberAnimLevelPending = cent->gent->client->ps.saberAnimLevel;
}
// don't need to draw ammo, but we will draw the current saber style in this window
switch ( cg.saberAnimLevelPending )
{
case 1://FORCE_LEVEL_1:
case 5://FORCE_LEVEL_5://Tavion
CG_DrawPic( x, y, 80, 40, cgs.media.HUDSaberStyleFast );
break;
case 2://FORCE_LEVEL_2:
CG_DrawPic( x, y, 80, 40, cgs.media.HUDSaberStyleMed );
break;
case 3://FORCE_LEVEL_3:
case 4://FORCE_LEVEL_4://Desann
CG_DrawPic( x, y, 80, 40, cgs.media.HUDSaberStyleStrong );
break;
}
return;
}
else
{
value = ps->ammo[weaponData[cent->currentState.weapon].ammoIndex];
}
if (value < 0) // No ammo
{
return;
}
//
// ammo
//
if (cg.oldammo < value)
{
cg.oldAmmoTime = cg.time + 200;
}
cg.oldammo = value;
// Firing or reloading?
if (( cg.predicted_player_state.weaponstate == WEAPON_FIRING
&& cg.predicted_player_state.weaponTime > 100 ))
{
numColor_i = CT_LTGREY;
}
else
{
if ( value > 0 )
{
if (cg.oldAmmoTime > cg.time)
{
numColor_i = CT_YELLOW;
}
else
{
numColor_i = CT_HUD_ORANGE;
}
}
else
{
numColor_i = CT_RED;
}
}
cgi_R_SetColor( colorTable[numColor_i] );
CG_DrawNumField(x + 29, y + 26, 3, value, 6, 12, NUM_FONT_SMALL,qfalse);
inc = (float) ammoData[weaponData[cent->currentState.weapon].ammoIndex].max / MAX_TICS;
value =ps->ammo[weaponData[cent->currentState.weapon].ammoIndex];
for (i=MAX_TICS-1;i>=0;i--)
{
if (value <= 0) // partial tic
{
memcpy(calcColor, colorTable[CT_BLACK], sizeof(vec4_t));
}
else if (value < inc) // partial tic
{
memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t));
percent = value / inc;
calcColor[0] *= percent;
calcColor[1] *= percent;
calcColor[2] *= percent;
}
else
{
memcpy(calcColor, colorTable[CT_WHITE], sizeof(vec4_t));
}
cgi_R_SetColor( calcColor);
CG_DrawPic( x + ammoTicPos[i].x,
y + ammoTicPos[i].y,
ammoTicPos[i].width,
ammoTicPos[i].height,
ammoTicPos[i].tic );
value -= inc;
}
}
/*
================
CG_DrawHUDLeftFrame1
================
*/
static void CG_DrawHUDLeftFrame1(int x,int y)
{
// Inner gray wire frame
cgi_R_SetColor( colorTable[CT_WHITE] );
CG_DrawPic( x, y, 80, 80, cgs.media.HUDInnerLeft );
}
/*
================
CG_DrawHUDLeftFrame2
================
*/
static void CG_DrawHUDLeftFrame2(int x,int y)
{
// Inner gray wire frame
cgi_R_SetColor( colorTable[CT_WHITE] );
CG_DrawPic( x, y, 80, 80, cgs.media.HUDLeftFrame ); // Metal frame
}
/*
================
CG_DrawHealth
================
*/
static void CG_DrawHealth(int x,int y)
{
vec4_t calcColor;
float healthPercent;
playerState_t *ps;
ps = &cg.snap->ps;
memcpy(calcColor, colorTable[CT_HUD_RED], sizeof(vec4_t));
healthPercent = (float) ps->stats[STAT_HEALTH]/ps->stats[STAT_MAX_HEALTH];
calcColor[0] *= healthPercent;
calcColor[1] *= healthPercent;
calcColor[2] *= healthPercent;
cgi_R_SetColor( calcColor);
CG_DrawPic( x, y, 80, 80, cgs.media.HUDHealth );
// Draw the ticks
if (cg.HUDHealthFlag)
{
cgi_R_SetColor( colorTable[CT_HUD_RED] );
CG_DrawPic( x, y, 80, 80, cgs.media.HUDHealthTic );
}
cgi_R_SetColor( colorTable[CT_HUD_RED] );
CG_DrawNumField (x + 16, y + 40, 3, ps->stats[STAT_HEALTH], 6, 12,
NUM_FONT_SMALL,qtrue);
}
/*
================
CG_DrawArmor
================
*/
static void CG_DrawArmor(int x,int y)
{
vec4_t calcColor;
float armorPercent,hold;
playerState_t *ps;
ps = &cg.snap->ps;
// Outer Armor circular
memcpy(calcColor, colorTable[CT_HUD_GREEN], sizeof(vec4_t));
hold = ps->stats[STAT_ARMOR]-(ps->stats[STAT_MAX_HEALTH]/2);
armorPercent = (float) hold/(ps->stats[STAT_MAX_HEALTH]/2);
if (armorPercent <0)
{
armorPercent = 0;
}
calcColor[0] *= armorPercent;
calcColor[1] *= armorPercent;
calcColor[2] *= armorPercent;
cgi_R_SetColor( calcColor);
CG_DrawPic( x, y, 80, 80, cgs.media.HUDArmor1 );
// Inner Armor circular
if (armorPercent>0)
{
armorPercent = 1;
}
else
{
armorPercent = (float) ps->stats[STAT_ARMOR]/(ps->stats[STAT_MAX_HEALTH]/2);
}
memcpy(calcColor, colorTable[CT_HUD_GREEN], sizeof(vec4_t));
calcColor[0] *= armorPercent;
calcColor[1] *= armorPercent;
calcColor[2] *= armorPercent;
cgi_R_SetColor( calcColor);
CG_DrawPic( x, y, 80, 80, cgs.media.HUDArmor2 ); // Inner Armor circular
/*
if (ps->stats[STAT_ARMOR]) // Is there armor? Draw the HUD Armor TIC
{
// Make tic flash if inner armor is at 50% (25% of full armor)
if (armorPercent<.5) // Do whatever the flash timer says
{
if (cg.HUDTickFlashTime < cg.time) // Flip at the same time
{
cg.HUDTickFlashTime = cg.time + 100;
if (cg.HUDArmorFlag)
{
cg.HUDArmorFlag = qfalse;
}
else
{
cg.HUDArmorFlag = qtrue;
}
}
}
else
{
cg.HUDArmorFlag=qtrue;
}
}
else // No armor? Don't show it.
{
cg.HUDArmorFlag=qfalse;
}
if (cg.HUDArmorFlag)
{
cgi_R_SetColor( colorTable[CT_HUD_GREEN] );
CG_DrawPic( x, y, 80, 80, cgs.media.HUDArmorTic );
}
*/
cgi_R_SetColor( colorTable[CT_HUD_GREEN] );
CG_DrawNumField (x + 16 + 14, y + 40 + 14, 3, ps->stats[STAT_ARMOR], 6, 12,
NUM_FONT_SMALL,qfalse);
}
//-----------------------------------------------------
static qboolean CG_DrawCustomHealthHud( centity_t *cent )
{
float health = 0;
vec4_t color;
if (( cent->currentState.eFlags & EF_LOCKED_TO_WEAPON ))
{
// DRAW emplaced HUD
color[0] = color[1] = color[2] = 0.0f;
color[3] = 0.3f;
cgi_R_SetColor( color );
CG_DrawPic( 14, 480 - 50, 94, 32, cgs.media.whiteShader );
// NOTE: this looks ugly
if ( cent->gent && cent->gent->owner )
{
if (( cent->gent->owner->flags & FL_GODMODE ))
{
// chair is in godmode, so render the health of the player instead
health = cent->gent->health / (float)cent->gent->max_health;
}
else
{
// render the chair health
health = cent->gent->owner->health / (float)cent->gent->owner->max_health;
}
}
color[0] = 1.0f;
color[3] = 0.5f;
cgi_R_SetColor( color );
CG_DrawPic( 18, 480 - 41, 87 * health, 19, cgs.media.whiteShader );
cgi_R_SetColor( colorTable[CT_WHITE] );
CG_DrawPic( 2, 480 - 64, 128, 64, cgs.media.emplacedHealthBarShader);
return qfalse; // drew this hud, so don't draw the player one
}
else if (( cent->currentState.eFlags & EF_IN_ATST ))
{
// we are an ATST...
color[0] = color[1] = color[2] = 0.0f;
color[3] = 0.3f;
cgi_R_SetColor( color );
CG_DrawPic( 14, 480 - 50, 94, 32, cgs.media.whiteShader );
// we just calc the display value from the sum of health and armor
if ( g_entities[cg.snap->ps.viewEntity].activator ) // ensure we can look back to the atst_drivable to get the max health
{
health = ( g_entities[cg.snap->ps.viewEntity].health + g_entities[cg.snap->ps.viewEntity].client->ps.stats[STAT_ARMOR] ) /
(float)(g_entities[cg.snap->ps.viewEntity].max_health + g_entities[cg.snap->ps.viewEntity].activator->max_health );
}
else
{
health = ( g_entities[cg.snap->ps.viewEntity].health + g_entities[cg.snap->ps.viewEntity].client->ps.stats[STAT_ARMOR]) /
(float)(g_entities[cg.snap->ps.viewEntity].max_health + 800 ); // hacked max armor since we don't have an activator...should never happen
}
color[1] = 0.25f; // blue-green
color[2] = 1.0f;
color[3] = 0.5f;
cgi_R_SetColor( color );
CG_DrawPic( 18, 480 - 41, 87 * health, 19, cgs.media.whiteShader );
cgi_R_SetColor( colorTable[CT_WHITE] );
CG_DrawPic( 2, 480 - 64, 128, 64, cgs.media.emplacedHealthBarShader);
return qfalse; // drew this hud, so don't draw the player one
}
else if ( cg.snap->ps.viewEntity && ( g_entities[cg.snap->ps.viewEntity].dflags & DAMAGE_CUSTOM_HUD ))
{
// if we've gotten this far, we are assuming that we are a misc_panel_turret
color[0] = color[1] = color[2] = 0.0f;
color[3] = 0.3f;
cgi_R_SetColor( color );
CG_DrawPic( 14, 480 - 50, 94, 32, cgs.media.whiteShader );
health = g_entities[cg.snap->ps.viewEntity].health / (float)g_entities[cg.snap->ps.viewEntity].max_health;
color[1] = 1.0f;
color[3] = 0.5f;
cgi_R_SetColor( color );
CG_DrawPic( 18, 480 - 41, 87 * health, 19, cgs.media.whiteShader );
cgi_R_SetColor( colorTable[CT_WHITE] );
CG_DrawPic( 2, 480 - 64, 128, 64, cgs.media.ladyLuckHealthShader );
return qfalse; // drew this hud, so don't draw the player one
}
return qtrue;
}
/*
==============
CG_DrawWeapReticle
==============
*/
static void CG_DrawWeapReticle( void )
{
vec4_t light_color = {0.7, 0.7, 0.7, 1};
vec4_t black = {0.0, 0.0, 0.0, 1};
float indent = 0.16;
float X_WIDTH=640;
float Y_HEIGHT=480;
float x = (X_WIDTH * indent), y = (Y_HEIGHT * indent), w = (X_WIDTH * (1-(2*indent))) / 2.0f, h = (Y_HEIGHT * (1-(2*indent))) / 2;
// sides
CG_FillRect( 0, 0, (X_WIDTH * indent), Y_HEIGHT, black );
CG_FillRect( X_WIDTH * (1 - indent), 0, (X_WIDTH * indent), Y_HEIGHT, black );
// top/bottom
CG_FillRect( X_WIDTH * indent, 0, X_WIDTH * (1-indent), Y_HEIGHT * indent, black );
CG_FillRect( X_WIDTH * indent, Y_HEIGHT * (1-indent), X_WIDTH * (1-indent), Y_HEIGHT * indent, black );
{
// center
if ( cgs.media.reticleShader ) {
cgi_R_DrawStretchPic( x, y, w, h, 0, 0, 1, 1, cgs.media.reticleShader ); // tl
cgi_R_DrawStretchPic( x + w, y, w, h, 1, 0, 0, 1, cgs.media.reticleShader ); // tr
cgi_R_DrawStretchPic( x, y + h, w, h, 0, 1, 1, 0, cgs.media.reticleShader ); // bl
cgi_R_DrawStretchPic( x + w, y + h, w, h, 1, 1, 0, 0, cgs.media.reticleShader ); // br
}
// hairs
CG_FillRect( 84, 239, 177, 2, black ); // left
CG_FillRect( 320, 242, 1, 58, black ); // center top
CG_FillRect( 319, 300, 2, 178, black ); // center bot
CG_FillRect( 380, 239, 177, 2, black ); // right
}
}
//--------------------------------------
static void CG_DrawBatteryCharge( void )
{
if ( cg.batteryChargeTime > cg.time )
{
vec4_t color;
// FIXME: drawing it here will overwrite zoom masks...find a better place
if ( cg.batteryChargeTime < cg.time + 1000 )
{
// fading out for the last second
color[0] = color[1] = color[2] = 1.0f;
color[3] = (cg.batteryChargeTime - cg.time) / 1000.0f;
}
else
{
// draw full
color[0] = color[1] = color[2] = color[3] = 1.0f;
}
cgi_R_SetColor( color );
// batteries were just charged
CG_DrawPic( 605, 295, 24, 32, cgs.media.batteryChargeShader );
}
}
/*
================
CG_DrawHUD
================
*/
extern void *cgi_UI_GetMenuByName( const char *menu );
extern void cgi_UI_Menu_Paint( void *menu, qboolean force );
static void CG_DrawHUD( centity_t *cent )
{
int x,y,value;
if (cgi_UI_GetMenuInfo("lefthud",&x,&y))
{
// Draw all the HUD elements --eez
cgi_UI_Menu_Paint( cgi_UI_GetMenuByName( "lefthud" ), qtrue );
// Draw armor & health values
if ( cg_draw2D.integer == 2 )
{
CG_DrawSmallStringColor(x+5, y - 60,va("Armor:%d",cg.snap->ps.stats[STAT_ARMOR]), colorTable[CT_HUD_GREEN] );
CG_DrawSmallStringColor(x+5, y - 40,va("Health:%d",cg.snap->ps.stats[STAT_HEALTH]), colorTable[CT_HUD_GREEN] );
}
CG_DrawHUDLeftFrame1(x,y);
CG_DrawArmor(x,y);
CG_DrawHealth(x,y);
CG_DrawHUDLeftFrame2(x,y);
}
if (cgi_UI_GetMenuInfo("righthud",&x,&y))
{
// Draw all the HUD elements --eez
cgi_UI_Menu_Paint( cgi_UI_GetMenuByName( "righthud" ), qtrue );
// Draw armor & health values
if ( cg_draw2D.integer == 2 )
{
if ( cent->currentState.weapon != WP_SABER && cent->currentState.weapon != WP_STUN_BATON && cent->gent )
{
// Bob, just didn't want the ammo text drawing when the saber or the stun baton is the current weapon...change it back if this is wrong.
value = cg.snap->ps.ammo[weaponData[cent->currentState.weapon].ammoIndex];
// value = cent->gent->client->ps.forcePower;
CG_DrawSmallStringColor(x, y - 60,va("Ammo:%d",value), colorTable[CT_HUD_GREEN] );
}
else
{
// value = cg.snap->ps.ammo[weaponData[cent->currentState.weapon].ammoIndex];
}
// CG_DrawSmallStringColor(x, y - 60,va("Ammo:%d",value), colorTable[CT_HUD_GREEN] );
CG_DrawSmallStringColor(x, y - 40,va("Force:%d",cent->gent->client->ps.forcePower), colorTable[CT_HUD_GREEN] );
}
CG_DrawHUDRightFrame1(x,y);
CG_DrawForcePower(cent,x,y);
CG_DrawAmmo(cent,x,y);
CG_DrawMessageLit(cent,x,y);
CG_DrawHUDRightFrame2(x,y);
}
}
/*
================
CG_ClearDataPadCvars
================
*/
void CG_ClearDataPadCvars( void )
{
cg_updatedDataPadForcePower1.integer = 0; //don't wait for the cvar-refresh.
cg_updatedDataPadForcePower2.integer = 0; //don't wait for the cvar-refresh.
cg_updatedDataPadForcePower3.integer = 0; //don't wait for the cvar-refresh.
cgi_Cvar_Set( "cg_updatedDataPadForcePower1", "0" );
cgi_Cvar_Set( "cg_updatedDataPadForcePower2", "0" );
cgi_Cvar_Set( "cg_updatedDataPadForcePower3", "0" );
cg_updatedDataPadObjective.integer = 0; //don't wait for the cvar-refresh.
cgi_Cvar_Set( "cg_updatedDataPadObjective", "0" );
}
/*
================
CG_DrawDataPadHUD
================
*/
void CG_DrawDataPadHUD( centity_t *cent )
{
int x,y;
x = 34;
y = 286;
CG_DrawHUDLeftFrame1(x,y);
CG_DrawArmor(x,y);
CG_DrawHealth(x,y);
x = 526;
if ((missionInfo_Updated) && ((cg_updatedDataPadForcePower1.integer) || (cg_updatedDataPadObjective.integer)))
{
// Stop flashing light
cg.missionInfoFlashTime = 0;
missionInfo_Updated = qfalse;
// Set which force power to show.
// cg_updatedDataPadForcePower is set from Q3_Interface, because force powers would only be given
// from a script.
if (cg_updatedDataPadForcePower1.integer)
{
cg.DataPadforcepowerSelect = cg_updatedDataPadForcePower1.integer - 1; // Not pretty, I know
if (cg.DataPadforcepowerSelect >= MAX_DPSHOWPOWERS)
{ //duh
cg.DataPadforcepowerSelect = MAX_DPSHOWPOWERS-1;
}
else if (cg.DataPadforcepowerSelect<0)
{
cg.DataPadforcepowerSelect=0;
}
}
// CG_ClearDataPadCvars();
}
CG_DrawHUDRightFrame1(x,y);
CG_DrawForcePower(cent,x,y);
CG_DrawAmmo(cent,x,y);
CG_DrawMessageLit(cent,x,y);
cgi_R_SetColor( colorTable[CT_WHITE]);
CG_DrawPic( 0, 0, 640, 480, cgs.media.dataPadFrame );
}
//------------------------
// CG_DrawZoomMask
//------------------------
static void CG_DrawBinocularNumbers( qboolean power )
{
vec4_t color1;
cgi_R_SetColor( colorTable[CT_BLACK]);
CG_DrawPic( 212, 367, 200, 40, cgs.media.whiteShader );
if ( power )
{
// Numbers should be kind of greenish
color1[0] = 0.2f;
color1[1] = 0.4f;
color1[2] = 0.2f;
color1[3] = 0.3f;
cgi_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.....
int val = ((int)((cg.refdefViewAngles[YAW] + 180) / 10)) * 10;
float off = (cg.refdefViewAngles[YAW] + 180) - val;
for ( int 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 );
}
}
/*
================
CG_DrawZoomMask
================
*/
extern float cg_zoomFov; //from cg_view.cpp
static void CG_DrawZoomMask( void )
{
vec4_t color1;
centity_t *cent;
float level;
static qboolean flip = qtrue;
float charge = cg.snap->ps.batteryCharge / (float)MAX_BATTERIES; // convert charge to a percentage
qboolean power = qfalse;
cent = &cg_entities[0];
if ( charge > 0.0f )
{
power = qtrue;
}
//-------------
// Binoculars
//--------------------------------
if ( cg.zoomMode == 1 )
{
CG_RegisterItemVisuals( ITM_BINOCULARS_PICKUP );
// zoom level
level = (float)(80.0f - cg_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;
if ( power )
{
// draw blue tinted distortion mask, trying to make it as small as is necessary to fill in the viewable area
cgi_R_SetColor( colorTable[CT_WHITE] );
CG_DrawPic( 34, 48, 570, 362, cgs.media.binocularStatic );
}
CG_DrawBinocularNumbers( power );
// Black out the area behind the battery display
cgi_R_SetColor( colorTable[CT_DKGREY]);
CG_DrawPic( 50, 389, 161, 16, cgs.media.whiteShader );
if ( power )
{
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;
cgi_R_SetColor( color1 );
CG_DrawPic( 82, 94, 16, 16, cgs.media.binocularCircle );
}
CG_DrawPic( 0, 0, 640, 480, cgs.media.binocularMask );
if ( power )
{
// Flickery color
color1[0] = 0.7f + Q_flrand(-1.0f, 1.0f) * 0.1f;
color1[1] = 0.8f + Q_flrand(-1.0f, 1.0f) * 0.1f;
color1[2] = 0.7f + Q_flrand(-1.0f, 1.0f) * 0.1f;
color1[3] = 1.0f;
cgi_R_SetColor( color1 );
CG_DrawPic( 4, 282 - level, 16, 16, cgs.media.binocularArrow );
}
else
{
// No power color
color1[0] = 0.15f;
color1[1] = 0.15f;
color1[2] = 0.15f;
color1[3] = 1.0f;
cgi_R_SetColor( color1 );
}
// The top triangle bit randomly flips when the power is on
if ( flip && power )
{
CG_DrawPic( 330, 60, -26, -30, cgs.media.binocularTri );
}
else
{
CG_DrawPic( 307, 40, 26, 30, cgs.media.binocularTri );
}
if ( Q_flrand(0.0f, 1.0f) > 0.98f && ( cg.time & 1024 ))
{
flip = (qboolean)!flip;
}
if ( power )
{
color1[0] = 1.0f * ( charge < 0.2f ? !!(cg.time & 256) : 1 );
color1[1] = charge * color1[0];
color1[2] = 0.0f;
color1[3] = 0.2f;
cgi_R_SetColor( color1 );
CG_DrawPic( 60, 394.5f, charge * 141, 5, cgs.media.whiteShader );
}
}
//------------
// Disruptor
//--------------------------------
else if ( cg.zoomMode == 2 )
{
level = (float)(80.0f - cg_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
cgi_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;
cgi_R_SetColor( color1 );
}
// Draw rotating insert
CG_DrawRotatePic2( 320, 240, 640, 480, -level, cgs.media.disruptorInsert );
float cx, cy;
float max;
max = cg_entities[0].gent->client->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 ammo, 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;
}
cgi_R_SetColor( color1 );
max *= 58.0f;
for ( float i = 18.5f; i <= 18.5f + max; i+= 3 ) // going from 15 to 45 degrees, with 5 degree increments
{
cx = 320 + sin( (i+90.0f)/57.296f ) * 190;
cy = 240 + cos( (i+90.0f)/57.296f ) * 190;
CG_DrawRotatePic2( cx, cy, 12, 24, 90 - i, cgs.media.disruptorInsertTick );
}
// FIXME: doesn't know about ammo!! which is bad because it draws charge beyond what ammo you may have..
if ( cg_entities[0].gent->client->ps.weaponstate == WEAPON_CHARGING_ALT )
{
cgi_R_SetColor( colorTable[CT_WHITE] );
// draw the charge level
max = ( cg.time - cg_entities[0].gent->client->ps.weaponChargeTime ) / ( 150.0f * 10.0f ); // bad hardcodedness 150 is disruptor charge unit and 10 is max charge units allowed.
if ( max > 1.0f )
{
max = 1.0f;
}
CG_DrawPic2( 257, 435, 134 * max, 34, 0,0,max,1,cgi_R_RegisterShaderNoMip( "gfx/2d/crop_charge" ));
}
}
//-----------
// Light Amp
//--------------------------------
else if ( cg.zoomMode == 3 )
{
CG_RegisterItemVisuals( ITM_LA_GOGGLES_PICKUP );
if ( power )
{
cgi_R_SetColor( colorTable[CT_WHITE] );
CG_DrawPic( 34, 29, 580, 410, cgs.media.laGogglesStatic );
CG_DrawPic( 570, 140, 12, 160, cgs.media.laGogglesSideBit );
float light = (128-cent->gent->lightLevel) * 0.5f;
if ( light < -81 ) // saber can really jack up local light levels....?magic number??
{
light = -81;
}
float pos1 = 220 + light;
float pos2 = 220 + cos( cg.time * 0.0004f + light * 0.05f ) * 40 + sin( cg.time * 0.0013f + 1 ) * 20 + sin( cg.time * 0.0021f ) * 5;
// Flickery color
color1[0] = 0.7f + Q_flrand(-1.0f, 1.0f) * 0.2f;
color1[1] = 0.8f + Q_flrand(-1.0f, 1.0f) * 0.2f;
color1[2] = 0.7f + Q_flrand(-1.0f, 1.0f) * 0.2f;
color1[3] = 1.0f;
cgi_R_SetColor( color1 );
CG_DrawPic( 565, pos1, 22, 8, cgs.media.laGogglesBracket );
CG_DrawPic( 558, pos2, 14, 5, cgs.media.laGogglesArrow );
}
// Black out the area behind the battery display
cgi_R_SetColor( colorTable[CT_DKGREY]);
CG_DrawPic( 236, 357, 164, 16, cgs.media.whiteShader );
if ( power )
{
// Power bar
color1[0] = 1.0f * ( charge < 0.2f ? !!(cg.time & 256) : 1 );
color1[1] = charge * color1[0];
color1[2] = 0.0f;
color1[3] = 0.4f;
cgi_R_SetColor( color1 );
CG_DrawPic( 247.0f, 362.5f, charge * 143.0f, 6, cgs.media.whiteShader );
// pulsing dot bit
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;
cgi_R_SetColor( color1 );
CG_DrawPic( 65, 94, 16, 16, cgs.media.binocularCircle );
}
CG_DrawPic( 0, 0, 640, 480, cgs.media.laGogglesMask );
}
}
/*
================
CG_DrawStats
================
*/
static void CG_DrawStats( void )
{
centity_t *cent;
if ( cg_drawStatus.integer == 0 ) {
return;
}
cent = &cg_entities[cg.snap->ps.clientNum];
if ((cg.snap->ps.viewEntity>0&&cg.snap->ps.viewEntity<ENTITYNUM_WORLD))
{
// MIGHT try and draw a custom hud if it wants...
CG_DrawCustomHealthHud( cent );
return;
}
cgi_UI_MenuPaintAll();
qboolean drawHud = qtrue;
if ( cent && cent->gent )
{
drawHud = CG_DrawCustomHealthHud( cent );
}
if (( drawHud ) && ( cg_drawHUD.integer ))
{
CG_DrawHUD( 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 );
cgi_R_SetColor( fadeColor );
CG_DrawPic( 573, 340, ICON_SIZE, ICON_SIZE, cg_items[ value ].icon );
//CG_DrawBigString( ICON_SIZE + 16, 398, bg_itemlist[ value ].classname, fadeColor[0] );
//CG_DrawProportionalString( ICON_SIZE + 16, 398,
// bg_itemlist[ value ].classname, CG_SMALLFONT,fadeColor );
cgi_R_SetColor( NULL );
}
}
}
void CMD_CGCam_Disable( void );
/*
===================
CG_DrawPickupItem
===================
*/
void CG_DrawCredits(void)
{
if (!cg.creditsStart)
{
//
cg.creditsStart = qtrue;
cgi_SP_Register("CREDITS", qfalse); // do not keep around after level
CG_Credits_Init("CREDITS_RAVEN", &colorTable[CT_ICON_BLUE]);
if ( cg_skippingcin.integer )
{//Were skipping a cinematic and it's over now
gi.cvar_set("timescale", "1");
gi.cvar_set("skippingCinematic", "0");
}
}
if (cg.creditsStart)
{
if ( !CG_Credits_Running() )
{
cgi_Cvar_Set( "cg_endcredits", "0" );
CMD_CGCam_Disable();
cgi_SendConsoleCommand("set nextmap disconnect ; cinematic outcast\n");
}
}
}
/*
================================================================================
CROSSHAIR
================================================================================
*/
/*
=================
CG_DrawCrosshair
=================
*/
static void CG_DrawCrosshair( vec3_t worldPoint )
{
float w, h;
qhandle_t hShader;
qboolean corona = qfalse;
vec4_t ecolor;
float f;
float x, y;
if ( !cg_drawCrosshair.integer )
{
return;
}
if ( cg.zoomMode > 0 && cg.zoomMode < 3 )
{
//not while scoped
return;
}
//set color based on what kind of ent is under crosshair
if ( g_crosshairEntNum >= ENTITYNUM_WORLD )
{
ecolor[0] = ecolor[1] = ecolor[2] = 1.0f;
}
else if ( cg_forceCrosshair && cg_crosshairForceHint.integer )
{
ecolor[0] = 0.2f;
ecolor[1] = 0.5f;
ecolor[2] = 1.0f;
corona = qtrue;
}
else if ( cg_crosshairIdentifyTarget.integer )
{
gentity_t *crossEnt = &g_entities[g_crosshairEntNum];
if ( crossEnt->client )
{
if ( crossEnt->client->ps.powerups[PW_CLOAKED] )
{//cloaked don't show up
ecolor[0] = 1.0f;//R
ecolor[1] = 1.0f;//G
ecolor[2] = 1.0f;//B
}
else if ( crossEnt->client->playerTeam == TEAM_PLAYER )
{
//Allies are green
ecolor[0] = 0.0f;//R
ecolor[1] = 1.0f;//G
ecolor[2] = 0.0f;//B
}
else if ( crossEnt->client->playerTeam == TEAM_NEUTRAL )
{
// NOTE: was yellow, but making it white unless they really decide they want to see colors
ecolor[0] = 1.0f;//R
ecolor[1] = 1.0f;//G
ecolor[2] = 1.0f;//B
}
else
{
//Enemies are red
ecolor[0] = 1.0f;//R
ecolor[1] = 0.1f;//G
ecolor[2] = 0.1f;//B
}
}
else if ( crossEnt->s.weapon == WP_TURRET && (crossEnt->svFlags&SVF_NONNPC_ENEMY) )
{
// a turret
if ( crossEnt->noDamageTeam == TEAM_PLAYER )
{
// mine are green
ecolor[0] = 0.0;//R
ecolor[1] = 1.0;//G
ecolor[2] = 0.0;//B
}
else
{
// hostile ones are red
ecolor[0] = 1.0;//R
ecolor[1] = 0.0;//G
ecolor[2] = 0.0;//B
}
}
else if ( crossEnt->s.weapon == WP_TRIP_MINE )
{
// tripmines are red
ecolor[0] = 1.0;//R
ecolor[1] = 0.0;//G
ecolor[2] = 0.0;//B
}
else
{
VectorCopy( crossEnt->startRGBA, ecolor );
if ( !ecolor[0] && !ecolor[1] && !ecolor[2] )
{
// We don't want a black crosshair, so use white since it will show up better
ecolor[0] = 1.0f;//R
ecolor[1] = 1.0f;//G
ecolor[2] = 1.0f;//B
}
}
}
else // cg_crosshairIdentifyTarget is not on, so make it white
{
ecolor[0] = ecolor[1] = ecolor[2] = 1.0f;
}
ecolor[3] = 1.0;
cgi_R_SetColor( ecolor );
if ( cg.forceCrosshairStartTime )
{
// both of these calcs will fade the corona in one direction
if ( cg.forceCrosshairEndTime )
{
ecolor[3] = (cg.time - cg.forceCrosshairEndTime) / 500.0f;
}
else
{
ecolor[3] = (cg.time - cg.forceCrosshairStartTime) / 300.0f;
}
// clamp
if ( ecolor[3] < 0 )
{
ecolor[3] = 0;
}
else if ( ecolor[3] > 1.0f )
{
ecolor[3] = 1.0f;
}
if ( !cg.forceCrosshairEndTime )
{
// but for the other direction, we'll need to reverse it
ecolor[3] = 1.0f - ecolor[3];
}
}
if ( corona ) // we are pointing at a crosshair item
{
if ( !cg.forceCrosshairStartTime )
{
// must have just happened because we are not fading in yet...start it now
cg.forceCrosshairStartTime = cg.time;
cg.forceCrosshairEndTime = 0;
}
if ( cg.forceCrosshairEndTime )
{
// must have just gone over a force thing again...and we were in the process of fading out. Set fade in level to the level where the fade left off
cg.forceCrosshairStartTime = cg.time - ( 1.0f - ecolor[3] ) * 300.0f;
cg.forceCrosshairEndTime = 0;
}
}
else // not pointing at a crosshair item
{
if ( cg.forceCrosshairStartTime && !cg.forceCrosshairEndTime ) // were currently fading in
{
// must fade back out, but we will have to set the fadeout time to be equal to the current level of faded-in-edness
cg.forceCrosshairEndTime = cg.time - ecolor[3] * 500.0f;
}
if ( cg.forceCrosshairEndTime && cg.time - cg.forceCrosshairEndTime > 500.0f ) // not pointing at anything and fade out is totally done
{
// reset everything
cg.forceCrosshairStartTime = 0;
cg.forceCrosshairEndTime = 0;
}
}
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
cgi_R_SetColor( NULL );
return;
}
x -= 320;//????
y -= 240;//????
}
else
{
x = cg_crosshairX.integer;
y = cg_crosshairY.integer;
}
if ( cg.snap->ps.viewEntity > 0 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD )
{
if ( !Q_stricmp( "misc_panel_turret", g_entities[cg.snap->ps.viewEntity].classname ))
{
// draws a custom crosshair that is twice as large as normal
cgi_R_DrawStretchPic( x + cg.refdef.x + 320 - w,
y + cg.refdef.y + 240 - h,
w * 2, h * 2, 0, 0, 1, 1, cgs.media.turretCrossHairShader );
}
}
else
{
hShader = cgs.media.crosshairShader[ cg_drawCrosshair.integer % NUM_CROSSHAIRS ];
cgi_R_DrawStretchPic( x + cg.refdef.x + 0.5 * (640 - w),
y + cg.refdef.y + 0.5 * (480 - h),
w, h, 0, 0, 1, 1, hShader );
}
if ( cg.forceCrosshairStartTime && cg_crosshairForceHint.integer ) // drawing extra bits
{
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;
cgi_R_SetColor( ecolor );
w *= 2.0f;
h *= 2.0f;
cgi_R_DrawStretchPic( x + cg.refdef.x + 0.5f * ( 640 - w ), y + cg.refdef.y + 0.5f * ( 480 - h ),
w, h,
0, 0, 1, 1,
cgs.media.forceCoronaShader );
}
cgi_R_SetColor( NULL );
}
/*
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)
{
vec3_t trans;
float xc, yc;
float px, py;
float z;
px = tan(cg.refdef.fov_x * (M_PI / 360) );
py = tan(cg.refdef.fov_y * (M_PI / 360) );
VectorSubtract(worldCoord, cg.refdef.vieworg, trans);
xc = 640 / 2.0;
yc = 480 / 2.0;
// z = how far is the object in our forward direction
z = DotProduct(trans, cg.refdef.viewaxis[0]);
if (z <= 0.001)
return qfalse;
*x = xc - DotProduct(trans, cg.refdef.viewaxis[1])*xc/(z*px);
*y = yc - DotProduct(trans, cg.refdef.viewaxis[2])*yc/(z*py);
return qtrue;
}
qboolean CG_WorldCoordToScreenCoord( vec3_t worldCoord, int *x, int *y ) {
float xF, yF;
if ( CG_WorldCoordToScreenCoordFloat( worldCoord, &xF, &yF ) ) {
*x = (int)xF;
*y = (int)yF;
return qtrue;
}
return qfalse;
}
// I'm keeping the rocket tracking code separate for now since I may want to do different logic...but it still uses trace info from scanCrosshairEnt
//-----------------------------------------
static void CG_ScanForRocketLock( void )
//-----------------------------------------
{
gentity_t *traceEnt;
static qboolean tempLock = qfalse; // this will break if anything else uses this locking code ( other than the player )
traceEnt = &g_entities[g_crosshairEntNum];
if ( !traceEnt || g_crosshairEntNum <= 0 || g_crosshairEntNum >= ENTITYNUM_WORLD || (!traceEnt->client && traceEnt->s.weapon != WP_TURRET ) || !traceEnt->health
|| ( traceEnt && traceEnt->client && traceEnt->client->ps.powerups[PW_CLOAKED] ))
{
// see how much locking we have
int dif = ( cg.time - g_rocketLockTime ) / ( 1200.0f / 8.0f );
// 8 is full locking....also if we just traced onto the world,
// give them 1/2 second of slop before dropping the lock
if ( dif < 8 && g_rocketSlackTime + 500 < cg.time )
{
// didn't have a full lock and not in grace period, so drop the lock
g_rocketLockTime = 0;
g_rocketSlackTime = 0;
tempLock = qfalse;
}
if ( g_rocketSlackTime + 500 >= cg.time && g_rocketLockEntNum < ENTITYNUM_WORLD )
{
// were locked onto an ent, aren't right now.....but still within the slop grace period
// keep the current lock amount
g_rocketLockTime += cg.frametime;
}
if ( !tempLock && g_rocketLockEntNum < ENTITYNUM_WORLD && dif >= 8 )
{
tempLock = qtrue;
if ( g_rocketLockTime + 1200 < cg.time )
{
g_rocketLockTime = cg.time - 1200; // doh, hacking the time so the targetting still gets drawn full
}
}
// keep locking to this thing for one second after it gets out of view
if ( g_rocketLockTime + 2000.0f < cg.time ) // since time was hacked above, I'm compensating so that 2000ms is really only 1000ms
{
// too bad, you had your chance
g_rocketLockEntNum = ENTITYNUM_NONE;
g_rocketSlackTime = 0;
g_rocketLockTime = 0;
}
}
else
{
tempLock = qfalse;
if ( g_rocketLockEntNum >= ENTITYNUM_WORLD )
{
if ( g_rocketSlackTime + 500 < cg.time )
{
// we just locked onto something, start the lock at the current time
g_rocketLockEntNum = g_crosshairEntNum;
g_rocketLockTime = cg.time;
g_rocketSlackTime = cg.time;
}
}
else
{
if ( g_rocketLockEntNum != g_crosshairEntNum )
{
g_rocketLockTime = cg.time;
}
// may as well always set this when we can potentially lock to something
g_rocketSlackTime = cg.time;
g_rocketLockEntNum = g_crosshairEntNum;
}
}
}
extern float forcePushPullRadius[];
void CG_ScanForForceCrosshairEntity( )
{
trace_t trace;
gentity_t *traceEnt = NULL;
vec3_t start, end;
int content;
int ignoreEnt = cg.snap->ps.clientNum;
//FIXME: debounce this to about 10fps?
cg_forceCrosshair = qfalse;
if ( cg_entities[0].gent && cg_entities[0].gent->client ) // <-Mike said it should always do this //if (cg_crosshairForceHint.integer &&
{//try to check for force-affectable stuff first
vec3_t angles, d_f, d_rt, d_up;
//VectorCopy( g_entities[0].client->renderInfo.eyePoint, start );
//AngleVectors( cg_entities[0].lerpAngles, d_f, d_rt, d_up );
BG_CalculateVROffHandPosition(start, angles);
AngleVectors( angles, d_f, d_rt, d_up );
VectorMA( start, 2048, d_f, end );//4028 is max for mind trick
//YES! This is very very bad... but it works! James made me do it. Really, he did. Blame James.
gi.trace( &trace, start, vec3_origin, vec3_origin, end,
ignoreEnt, MASK_OPAQUE|CONTENTS_SHOTCLIP|CONTENTS_BODY|CONTENTS_ITEM, G2_NOCOLLIDE, 10 );// ); took out CONTENTS_SOLID| so you can target people through glass.... took out CONTENTS_CORPSE so disintegrated guys aren't shown, could just remove their body earlier too...
if ( trace.entityNum < ENTITYNUM_WORLD )
{//hit something
traceEnt = &g_entities[trace.entityNum];
if ( traceEnt )
{
if ( traceEnt->client)
{//is a client
cg_forceCrosshair = qtrue;
}
// No? Check for force-push/pullable doors and func_statics
else if ( traceEnt->s.eType == ET_MOVER )
{//hit a mover
if ( !Q_stricmp( "func_door", traceEnt->classname ) )
{//it's a func_door
if ( traceEnt->spawnflags & 2/*MOVER_FORCE_ACTIVATE*/ )
{//it's force-usable
if ( cg_entities[0].gent->client->ps.forcePowerLevel[FP_PULL] || cg_entities[0].gent->client->ps.forcePowerLevel[FP_PUSH] )
{//player has push or pull
float maxRange;
if ( cg_entities[0].gent->client->ps.forcePowerLevel[FP_PULL] > cg_entities[0].gent->client->ps.forcePowerLevel[FP_PUSH] )
{//use the better range
maxRange = forcePushPullRadius[cg_entities[0].gent->client->ps.forcePowerLevel[FP_PULL]];
}
else
{//use the better range
maxRange = forcePushPullRadius[cg_entities[0].gent->client->ps.forcePowerLevel[FP_PUSH]];
}
if ( maxRange >= trace.fraction * 2048 )
{//actually close enough to use one of our force powers on it
cg_forceCrosshair = qtrue;
}
}
}
}
else if ( !Q_stricmp( "func_static", traceEnt->classname ) )
{//it's a func_static
if ( (traceEnt->spawnflags & 1/*F_PUSH*/) && (traceEnt->spawnflags & 2/*F_PULL*/) )
{//push or pullable
float maxRange;
if ( cg_entities[0].gent->client->ps.forcePowerLevel[FP_PULL] > cg_entities[0].gent->client->ps.forcePowerLevel[FP_PUSH] )
{//use the better range
maxRange = forcePushPullRadius[cg_entities[0].gent->client->ps.forcePowerLevel[FP_PULL]];
}
else
{//use the better range
maxRange = forcePushPullRadius[cg_entities[0].gent->client->ps.forcePowerLevel[FP_PUSH]];
}
if ( maxRange >= trace.fraction * 2048 )
{//actually close enough to use one of our force powers on it
cg_forceCrosshair = qtrue;
}
}
else if ( (traceEnt->spawnflags & 1/*F_PUSH*/) )
{//pushable only
if ( forcePushPullRadius[cg_entities[0].gent->client->ps.forcePowerLevel[FP_PUSH]] >= trace.fraction * 2048 )
{//actually close enough to use force push on it
cg_forceCrosshair = qtrue;
}
}
else if ( (traceEnt->spawnflags & 2/*F_PULL*/) )
{//pullable only
if ( forcePushPullRadius[cg_entities[0].gent->client->ps.forcePowerLevel[FP_PULL]] >= trace.fraction * 2048 )
{//actually close enough to use force pull on it
cg_forceCrosshair = qtrue;
}
}
}
}
}
}
}
if ( !traceEnt || (traceEnt->s.eFlags & EF_NO_TED) )
{
return;
}
// if the player is in fog, don't show it
content = cgi_CM_PointContents( trace.endpos, 0 );
if ( content & CONTENTS_FOG )
{
return;
}
// if the player is cloaked, don't show it
if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_CLOAKED ))
{
return;
}
// update the fade timer
if ( cg.crosshairClientNum != trace.entityNum )
{
infoStringCount = 0;
}
cg.crosshairClientNum = trace.entityNum;
cg.crosshairClientTime = cg.time;
}
/*
=================
CG_DrawCrosshair3D
=================
*/
static void CG_DrawCrosshair3D(int type) // 0 - force, 1 - weapons
{
float w;
qhandle_t hShader;
float f;
int ca;
trace_t trace;
vec3_t endpos;
refEntity_t ent;
if (( type == 1 && !cg_drawCrosshair.integer) ||
(type == 0 && !cg_drawCrosshairForce.integer)) {
return;
}
if (cg.snap->ps.pm_type == PM_INTERMISSION)
{
return;
}
if ( in_camera ) {
return;
}
if ( cg.zoomMode )
{
//not while scoped
return;
}
if ( in_misccamera )
{
//Not while viewing from another entity (such as a camera)
return;
}
if ( type == 1 && (cg.snap->ps.weapon == WP_NONE ||
cg.snap->ps.weapon == WP_MELEE ||
cg.snap->ps.weapon == WP_SABER ||
cg.snap->ps.weapon == WP_STUN_BATON ||
cg.snap->ps.weapon == WP_THERMAL ))
{
return;
}
if (type == 0)
{
if (showPowers[cg.forcepowerSelect] == FP_HEAL ||
showPowers[cg.forcepowerSelect] == FP_SPEED ||
vr->weapon_stabilised)
{
return;
}
CG_ScanForForceCrosshairEntity();
}
w = 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 );
}
ca = (type == 1) ? cg_drawCrosshair.integer : cg_drawCrosshairForce.integer;
if (ca < 0) {
ca = 0;
}
hShader = cgs.media.crosshairShader[ ca % NUM_CROSSHAIRS ];
float xmax = 64.0f * tan(cg.refdef.fov_x * M_PI / 360.0f);
vec3_t forward, weaponangles, origin;
if (type == 0)
{
BG_CalculateVROffHandPosition(origin, weaponangles);
}
else
{
BG_CalculateVRWeaponPosition(origin, weaponangles);
}
AngleVectors(weaponangles, forward, NULL, NULL);
VectorMA(origin, 2048, forward, endpos);
CG_Trace(&trace, origin, NULL, NULL, endpos, 0, MASK_SHOT);
if (trace.fraction != 1.0f) {
memset(&ent, 0, sizeof(ent));
ent.reType = RT_SPRITE;
ent.renderfx = RF_FIRST_PERSON;
VectorCopy(trace.endpos, ent.origin);
ent.radius = w / 640 * xmax * trace.fraction * 2048 / 64.0f;
ent.customShader = hShader;
if(type == 0 && !cg_forceCrosshair) //Not Active Force Crosshair
{
ent.shaderRGBA[0] = 255;
ent.shaderRGBA[1] = 180;
ent.shaderRGBA[2] = 180;
}
else if(type == 0) //Active Force Crosshair
{
ent.shaderRGBA[0] = 255;
ent.shaderRGBA[1] = 90;
ent.shaderRGBA[2] = 90;
}
else //Regular Crosshair
{
ent.shaderRGBA[0] = 255;
ent.shaderRGBA[1] = 255;
ent.shaderRGBA[2] = 255;
}
ent.shaderRGBA[3] = 255;
cgi_R_AddRefEntityToScene(&ent);
}
}
/*
=================
CG_ScanForCrosshairEntity
=================
*/
static void CG_ScanForCrosshairEntity( qboolean scanAll )
{
trace_t trace;
gentity_t *traceEnt = NULL;
vec3_t start, end;
int content;
int ignoreEnt = cg.snap->ps.clientNum;
//FIXME: debounce this to about 10fps?
cg_forceCrosshair = qfalse;
if ( cg_entities[0].gent && cg_entities[0].gent->client ) // <-Mike said it should always do this //if (cg_crosshairForceHint.integer &&
{//try to check for force-affectable stuff first
vec3_t d_f, d_rt, d_up;
//VectorCopy( g_entities[0].client->renderInfo.eyePoint, start );
//AngleVectors( cg_entities[0].lerpAngles, d_f, d_rt, d_up );
VectorMA( start, 2048, d_f, end );//4028 is max for mind trick
//YES! This is very very bad... but it works! James made me do it. Really, he did. Blame James.
gi.trace( &trace, start, vec3_origin, vec3_origin, end,
ignoreEnt, MASK_OPAQUE|CONTENTS_SHOTCLIP|CONTENTS_BODY|CONTENTS_ITEM, G2_NOCOLLIDE, 10 );// ); took out CONTENTS_SOLID| so you can target people through glass.... took out CONTENTS_CORPSE so disintegrated guys aren't shown, could just remove their body earlier too...
if ( trace.entityNum < ENTITYNUM_WORLD )
{//hit something
traceEnt = &g_entities[trace.entityNum];
if ( traceEnt )
{
// Check for mind trickable-guys
if ( traceEnt->client )
{//is a client
if ( cg_entities[0].gent->client->ps.forcePowerLevel[FP_TELEPATHY] && traceEnt->health > 0 && VALIDSTRING(traceEnt->behaviorSet[BSET_MINDTRICK]) )
{//I have the ability to mind-trick and he is alive and he has a mind trick script
//NOTE: no need to check range since it's always 2048
cg_forceCrosshair = qtrue;
}
}
// No? Check for force-push/pullable doors and func_statics
else if ( traceEnt->s.eType == ET_MOVER )
{//hit a mover
if ( !Q_stricmp( "func_door", traceEnt->classname ) )
{//it's a func_door
if ( traceEnt->spawnflags & 2/*MOVER_FORCE_ACTIVATE*/ )
{//it's force-usable
if ( cg_entities[0].gent->client->ps.forcePowerLevel[FP_PULL] || cg_entities[0].gent->client->ps.forcePowerLevel[FP_PUSH] )
{//player has push or pull
float maxRange;
if ( cg_entities[0].gent->client->ps.forcePowerLevel[FP_PULL] > cg_entities[0].gent->client->ps.forcePowerLevel[FP_PUSH] )
{//use the better range
maxRange = forcePushPullRadius[cg_entities[0].gent->client->ps.forcePowerLevel[FP_PULL]];
}
else
{//use the better range
maxRange = forcePushPullRadius[cg_entities[0].gent->client->ps.forcePowerLevel[FP_PUSH]];
}
if ( maxRange >= trace.fraction * 2048 )
{//actually close enough to use one of our force powers on it
cg_forceCrosshair = qtrue;
}
}
}
}
else if ( !Q_stricmp( "func_static", traceEnt->classname ) )
{//it's a func_static
if ( (traceEnt->spawnflags & 1/*F_PUSH*/) && (traceEnt->spawnflags & 2/*F_PULL*/) )
{//push or pullable
float maxRange;
if ( cg_entities[0].gent->client->ps.forcePowerLevel[FP_PULL] > cg_entities[0].gent->client->ps.forcePowerLevel[FP_PUSH] )
{//use the better range
maxRange = forcePushPullRadius[cg_entities[0].gent->client->ps.forcePowerLevel[FP_PULL]];
}
else
{//use the better range
maxRange = forcePushPullRadius[cg_entities[0].gent->client->ps.forcePowerLevel[FP_PUSH]];
}
if ( maxRange >= trace.fraction * 2048 )
{//actually close enough to use one of our force powers on it
cg_forceCrosshair = qtrue;
}
}
else if ( (traceEnt->spawnflags & 1/*F_PUSH*/) )
{//pushable only
if ( forcePushPullRadius[cg_entities[0].gent->client->ps.forcePowerLevel[FP_PUSH]] >= trace.fraction * 2048 )
{//actually close enough to use force push on it
cg_forceCrosshair = qtrue;
}
}
else if ( (traceEnt->spawnflags & 2/*F_PULL*/) )
{//pullable only
if ( forcePushPullRadius[cg_entities[0].gent->client->ps.forcePowerLevel[FP_PULL]] >= trace.fraction * 2048 )
{//actually close enough to use force pull on it
cg_forceCrosshair = qtrue;
}
}
}
}
}
}
}
if ( !cg_forceCrosshair )
{
if ( cg_dynamicCrosshair.integer )
{//100% accurate
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 )
{
if ( cg.snap->ps.viewEntity > 0 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD )
{//in camera ent view
ignoreEnt = cg.snap->ps.viewEntity;
if ( g_entities[cg.snap->ps.viewEntity].client )
{
VectorCopy( g_entities[cg.snap->ps.viewEntity].client->renderInfo.eyePoint, start );
}
else
{
VectorCopy( cg_entities[cg.snap->ps.viewEntity].lerpOrigin, start );
}
AngleVectors( cg_entities[cg.snap->ps.viewEntity].lerpAngles, d_f, d_rt, d_up );
}
else
{
VectorCopy( g_entities[0].client->renderInfo.eyePoint, start );
AngleVectors( cg_entities[0].lerpAngles, d_f, d_rt, d_up );
}
}
else
{
extern void CalcMuzzlePoint( gentity_t *const ent, vec3_t forward, vec3_t right, vec3_t up, vec3_t muzzlePoint, float lead_in );
AngleVectors( cg_entities[0].lerpAngles, d_f, d_rt, d_up );
CalcMuzzlePoint( &g_entities[0], d_f, d_rt, d_up, start , 0 );
}
//VectorCopy( g_entities[0].client->renderInfo.muzzlePoint, start );
//FIXME: increase this? Increase when zoom in?
VectorMA( start, 4096, d_f, end );//was 8192
}
else
{//old way
VectorCopy( cg.refdef.vieworg, start );
//FIXME: increase this? Increase when zoom in?
VectorMA( start, 4096, cg.refdef.viewaxis[0], end );//was 8192
}
//YES! This is very very bad... but it works! James made me do it. Really, he did. Blame James.
gi.trace( &trace, start, vec3_origin, vec3_origin, end,
ignoreEnt, MASK_OPAQUE|CONTENTS_SHOTCLIP|CONTENTS_BODY|CONTENTS_ITEM, G2_NOCOLLIDE, 10 );// ); took out CONTENTS_SOLID| so you can target people through glass.... took out CONTENTS_CORPSE so disintegrated guys aren't shown, could just remove their body earlier too...
/*
CG_Trace( &trace, start, vec3_origin, vec3_origin, end,
cg.snap->ps.clientNum, MASK_PLAYERSOLID|CONTENTS_CORPSE|CONTENTS_ITEM );
*/
//FIXME: pick up corpses
if ( trace.startsolid || trace.allsolid )
{
// trace should not be allowed to pick up anything if it started solid. I tried actually moving the trace start back, which also worked,
// but the dynamic cursor drawing caused it to render around the clip of the gun when I pushed the blaster all the way into a wall.
// It looked quite horrible...but, if this is bad for some reason that I don't know
trace.entityNum = ENTITYNUM_NONE;
}
traceEnt = &g_entities[trace.entityNum];
}
// if the object is "dead", don't show it
/* if ( cg.crosshairClientNum && g_entities[cg.crosshairClientNum].health <= 0 )
{
cg.crosshairClientNum = 0;
return;
}
*/
if ( cg_entities[cg.snap->ps.clientNum].currentState.eFlags & EF_LOCKED_TO_WEAPON ||
(!Q_stricmp( "misc_panel_turret", g_entities[cg.snap->ps.viewEntity].classname ))) {
//draw crosshair at endpoint
CG_DrawCrosshair(trace.endpos);
}
g_crosshairEntNum = trace.entityNum;
g_crosshairEntDist = 4096*trace.fraction;
if ( !traceEnt )
{
//not looking at anything
g_crosshairSameEntTime = 0;
g_crosshairEntTime = 0;
}
else
{//looking at a valid ent
//store the distance
if ( trace.entityNum != g_crosshairEntNum )
{//new crosshair ent
g_crosshairSameEntTime = 0;
}
else if ( g_crosshairEntDist < 256 )
{//close enough to start counting how long you've been looking
g_crosshairSameEntTime += cg.frametime;
}
//remember the last time you looked at the person
g_crosshairEntTime = cg.time;
}
if ( !traceEnt || (traceEnt->s.eFlags & EF_NO_TED) )
{
if ( traceEnt && scanAll )
{
}
else
{
return;
}
}
// if the player is in fog, don't show it
content = cgi_CM_PointContents( trace.endpos, 0 );
if ( content & CONTENTS_FOG )
{
return;
}
// if the player is cloaked, don't show it
if ( cg_entities[ trace.entityNum ].currentState.powerups & ( 1 << PW_CLOAKED ))
{
return;
}
// update the fade timer
if ( cg.crosshairClientNum != trace.entityNum )
{
infoStringCount = 0;
}
cg.crosshairClientNum = trace.entityNum;
cg.crosshairClientTime = cg.time;
}
/*
=====================
CG_DrawCrosshairNames
=====================
*/
static void CG_DrawCrosshairNames( void )
{
qboolean scanAll = qfalse;
centity_t *player = &cg_entities[0];
if ( cg_dynamicCrosshair.integer )
{
// still need to scan for dynamic crosshair
CG_ScanForCrosshairEntity( scanAll );
return;
}
if ( !player->gent )
{
return;
}
if ( !player->gent->client )
{
return;
}
// scan the known entities to see if the crosshair is sighted on one
// This is currently being called by the rocket tracking code, so we don't necessarily want to do duplicate traces :)
CG_ScanForCrosshairEntity( scanAll );
}
//--------------------------------------------------------------
static void CG_DrawRocketLocking( int lockEntNum, int lockTime )
//--------------------------------------------------------------
{
gentity_t *gent = &g_entities[lockEntNum];
if ( !gent )
{
return;
}
int cx, cy;
vec3_t org;
static int oldDif = 0;
VectorCopy( gent->currentOrigin, org );
org[2] += (gent->mins[2] + gent->maxs[2]) * 0.5f;
if ( CG_WorldCoordToScreenCoord( org, &cx, &cy ))
{
// we care about distance from enemy to eye, so this is good enough
float sz = Distance( gent->currentOrigin, cg.refdef.vieworg ) / 1024.0f;
if ( cg.zoomMode > 0 )
{
if ( cg.overrides.active & CG_OVERRIDE_FOV )
{
sz -= ( cg.overrides.fov - cg_zoomFov ) / 80.0f;
}
else
{
sz -= ( cg_fov.value - cg_zoomFov ) / 80.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;
vec4_t color={0.0f,0.0f,0.0f,0.0f};
cy += sz * 0.5f;
// well now, take our current lock time and divide that by 8 wedge slices to get the current lock amount
int dif = ( cg.time - g_rocketLockTime ) / ( 1200.0f / 8.0f );
if ( dif < 0 )
{
oldDif = 0;
return;
}
else if ( dif > 8 )
{
dif = 8;
}
// do sounds
if ( oldDif != dif )
{
if ( dif == 8 )
{
cgi_S_StartSound( org, 0, CHAN_AUTO, cgi_S_RegisterSound( "sound/weapons/rocket/lock.wav" ));
}
else
{
cgi_S_StartSound( org, 0, CHAN_AUTO, cgi_S_RegisterSound( "sound/weapons/rocket/tick.wav" ));
}
}
oldDif = dif;
for ( int i = 0; i < dif; i++ )
{
color[0] = 1.0f;
color[1] = 0.0f;
color[2] = 0.0f;
color[3] = 0.1f * i + 0.2f;
cgi_R_SetColor( color );
// our slices are offset by about 45 degrees.
CG_DrawRotatePic( cx - sz, cy - sz, sz, sz, i * 45.0f, cgi_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
cgi_R_SetColor( color );
CG_DrawPic( cx - sz, cy - sz * 2, sz * 2, sz * 2, cgi_R_RegisterShaderNoMip( "gfx/2d/lock" ));
}
}
}
//------------------------------------
static void CG_RunRocketLocking( void )
//------------------------------------
{
centity_t *player = &cg_entities[0];
// Only bother with this when the player is holding down the alt-fire button of the rocket launcher
if ( player->currentState.weapon == WP_ROCKET_LAUNCHER )
{
if ( player->currentState.eFlags & EF_ALT_FIRING )
{
CG_ScanForRocketLock();
if ( g_rocketLockEntNum > 0 && g_rocketLockEntNum < ENTITYNUM_WORLD && g_rocketLockTime > 0 )
{
CG_DrawRocketLocking( g_rocketLockEntNum, g_rocketLockTime );
}
}
else
{
// disengage any residual locking
g_rocketLockEntNum = ENTITYNUM_WORLD;
g_rocketLockTime = 0;
}
}
}
/*
=================
CG_DrawIntermission
=================
*/
static void CG_DrawIntermission( void ) {
CG_DrawScoreboard();
}
/*
==================
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 = cgi_R_Font_StrLenPixels(s, cgs.media.qhFontSmall, FONT_SCALE);
int tempX = 635 - w;
int tempY = y+2;
cgi_R_Font_DrawString(tempX, tempY, s, colorTable[CT_LTGOLD1], cgs.media.qhFontSmall, -1, FONT_SCALE);
return y + BIGCHAR_HEIGHT + 10;
}
/*
==================
CG_DrawFPS
==================
*/
#define FPS_FRAMES 16
static float CG_DrawFPS( float y ) {
char *s;
static unsigned short previousTimes[FPS_FRAMES];
static unsigned short index;
static int previous, lastupdate;
int t, i, fps, total;
unsigned short frameTime;
// don't use serverTime, because that will be drifting to
// correct for internet lag changes, timescales, timedemos, etc
t = cgi_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 );
const int w = cgi_R_Font_StrLenPixels(s, cgs.media.qhFontSmall, FONT_SCALE);
int tempX = 635 - w;
int tempY = y+2;
cgi_R_Font_DrawString(tempX, tempY, s, colorTable[CT_LTGOLD1], cgs.media.qhFontSmall, -1, FONT_SCALE);
return y + BIGCHAR_HEIGHT + 10;
}
/*
=================
CG_DrawTimer
=================
*/
static float CG_DrawTimer( float y ) {
char *s;
int w;
int mins, seconds, tens;
seconds = cg.time / 1000;
mins = seconds / 60;
seconds -= mins * 60;
tens = seconds / 10;
seconds -= tens * 10;
s = va( "%i:%i%i", mins, tens, seconds );
w = cgi_R_Font_StrLenPixels(s, cgs.media.qhFontSmall, FONT_SCALE);
int tempX = 635 - w;
int tempY = y+2;
cgi_R_Font_DrawString(tempX, tempY, s, colorTable[CT_LTGOLD1], cgs.media.qhFontSmall, -1, FONT_SCALE);
return y + BIGCHAR_HEIGHT + 10;
}
/*
=================
CG_DrawAmmoWarning
=================
*/
static void CG_DrawAmmoWarning( void ) {
char text[1024]={0};
int w;
if ( cg_drawAmmoWarning.integer == 0 ) {
return;
}
if ( !cg.lowAmmoWarning ) {
return;
}
if ( weaponData[cg.snap->ps.weapon].ammoIndex == AMMO_NONE )
{//doesn't use ammo, so no warning
return;
}
if ( cg.lowAmmoWarning == 2 ) {
cgi_SP_GetStringTextString( "INGAME_INSUFFICIENTENERGY", text, sizeof(text) );
} else {
return;
//s = "LOW AMMO WARNING";
}
w = cgi_R_Font_StrLenPixels(text,cgs.media.qhFontSmall, FONT_SCALE);
int offset = w / 2;
int tempX = SCREEN_WIDTH / 2;
int tempY = 64;
cgi_R_Font_DrawString(tempX - offset, tempY, text, colorTable[CT_LTGOLD1], cgs.media.qhFontSmall, -1, FONT_SCALE);
}
//---------------------------------------
static qboolean CG_RenderingFromMiscCamera()
{
//centity_t *cent;
//cent = &cg_entities[cg.snap->ps.clientNum];
if ( cg.snap->ps.viewEntity > 0 &&
cg.snap->ps.viewEntity < ENTITYNUM_WORLD )// cent && cent->gent && cent->gent->client && cent->gent->client->ps.viewEntity)
{
// Only check viewEntities
if ( !Q_stricmp( "misc_camera", g_entities[cg.snap->ps.viewEntity].classname ))
{
// Only doing a misc_camera, so check health.
if ( g_entities[cg.snap->ps.viewEntity].health > 0 )
{
CG_DrawPic( 0, 0, 640, 480, cgi_R_RegisterShader( "gfx/2d/workingCamera" ));
}
else
{
CG_DrawPic( 0, 0, 640, 480, cgi_R_RegisterShader( "gfx/2d/brokenCamera" ));
}
// don't render other 2d stuff
return qtrue;
}
if (vr->remote_droid)
{
//Render as if we are looking through a camera
CG_DrawPic( 0, 0, 640, 480, cgi_R_RegisterShader( "gfx/2d/workingCamera" ));
}
else if ( !Q_stricmp( "misc_panel_turret", g_entities[cg.snap->ps.viewEntity].classname ))
{
// could do a panel turret screen overlay...this is a cheesy placeholder
CG_DrawPic( 30, 90, 128, 300, cgs.media.turretComputerOverlayShader );
CG_DrawPic( 610, 90, -128, 300, cgs.media.turretComputerOverlayShader );
}
else
{
// FIXME: make sure that this assumption is correct...because I'm assuming that I must be a droid.
CG_DrawPic( 0, 0, 640, 480, cgi_R_RegisterShader( "gfx/2d/droid_view" ));
}
}
// not in misc_camera, render other stuff.
return qfalse;
}
qboolean cg_usingInFrontOf = qfalse;
qboolean CanUseInfrontOf(gentity_t*);
static void CG_UseIcon()
{
if (cg_usableObjectsHint.integer) {
cg_usingInFrontOf = CanUseInfrontOf(cg_entities[cg.snap->ps.clientNum].gent);
if (cg_usingInFrontOf)
{
cgi_R_SetColor( NULL );
CG_DrawPic( 50, 285, 64, 64, cgs.media.useableHint );
}
}
}
/*
-------------------------
CG_DrawZoomBorders
-------------------------
*/
static void CG_DrawZoomBorders( void )
{
vec4_t modulate;
modulate[0] = modulate[1] = modulate[2] = 0.0f;
modulate[3] = 1.0f;
int bar_height = 80;
CG_FillRect( 0, 0, 640, bar_height, modulate );
CG_FillRect( 0, 480 - 80, 640, bar_height, modulate );
}
/*
==============
CG_DrawVignette
==============
*/
float currentComfortVignetteValue = 0.0f;
float filteredViewYawDelta = 0.0f;
static void CG_DrawVignette( void )
{
playerState_t *ps;
ps = &cg.snap->ps;
cvar_t *vr_comfort_vignette = gi.cvar("vr_comfort_vignette", "0.0", CVAR_ARCHIVE); // defined in VrCvars.h
if (vr_comfort_vignette->value <= 0.0f || vr_comfort_vignette->value > 1.0f || !cg.zoomMode == 0)
{
return;
}
bool isMoving = VectorLength(cg.predicted_player_state.velocity) > 30.0;
// When player is in the air, apply vignette (to prevent throbbing on top of jump)
bool isInAir = ps->groundEntityNum == ENTITYNUM_NONE;
cvar_t *vr_turn_mode = gi.cvar("vr_turn_mode", "0", CVAR_ARCHIVE); // defined in VrCvars.h
// Apply only for smooth turn
bool isTurning = (vr_turn_mode->integer == 2 || (vr_turn_mode->integer == 1 && vr->third_person));
if (isTurning) {
float yawDelta = fabsf(vr->clientview_yaw_delta);
if (yawDelta > 180)
{
yawDelta = fabs(yawDelta - 360);
}
filteredViewYawDelta = filteredViewYawDelta * 0.75f + yawDelta * 0.25f;
isTurning = filteredViewYawDelta > 1;
}
if (isMoving || isInAir || isTurning)
{
if (currentComfortVignetteValue < vr_comfort_vignette->value)
{
currentComfortVignetteValue += vr_comfort_vignette->value * 0.05;
if (currentComfortVignetteValue > 1.0f)
currentComfortVignetteValue = 1.0f;
}
} else{
if (currentComfortVignetteValue > 0.0f)
currentComfortVignetteValue -= vr_comfort_vignette->value * 0.05;
}
if (currentComfortVignetteValue > 0.0f && currentComfortVignetteValue <= 1.0f)
{
int screenWidth = 640; //cg.refdef.width;
int screenHeight = 480; //cg.refdef.height;
int x = (int)(0 + currentComfortVignetteValue * screenWidth / 3.5f);
int w = (int)(screenWidth - 2 * x);
int y = (int)(0 + currentComfortVignetteValue * screenHeight / 3.5f);
int h = (int)(screenHeight - 2 * y);
vec4_t black = {0.0, 0.0, 0.0, 1};
cgi_R_SetColor( black );
// sides
cgi_R_DrawStretchPic( 0, 0, x, screenHeight, 0, 0, 1, 1, cgs.media.whiteShader );
cgi_R_DrawStretchPic( screenWidth - x, 0, x, screenHeight, 0, 0, 1, 1, cgs.media.whiteShader );
// top/bottom
cgi_R_DrawStretchPic( x, 0, screenWidth - x, y, 0, 0, 1, 1, cgs.media.whiteShader );
cgi_R_DrawStretchPic( x, screenHeight - y, screenWidth - x, y, 0, 0, 1, 1, cgs.media.whiteShader );
// vignette
cgi_R_DrawStretchPic( x, y, w, h, 0, 0, 1, 1, cgs.media.vignetteShader );
cgi_R_SetColor( NULL );
}
}
/*
=================
CG_Draw2D
=================
*/
extern void CG_SaberClashFlare( void );
static void CG_Draw2D( void )
{
char text[1024]={0};
int w,y_pos;
centity_t *cent = &cg_entities[cg.snap->ps.clientNum];
// if we are taking a levelshot for the menu, don't draw anything
if ( cg.levelShot )
{
return;
}
if ( cg_draw2D.integer == 0 )
{
return;
}
if ( cg.snap->ps.pm_type == PM_INTERMISSION )
{
cg.drawingHUD = CG_HUD_SCALED;
CG_DrawIntermission();
cg.drawingHUD = CG_HUD_NORMAL;
return;
}
if (cg_endcredits.integer)
{
if (!CG_Credits_Draw())
{
CG_DrawCredits(); // will probably get rid of this soon
}
}
if (!vr->immersive_cinematics) {
CGCam_DrawWideScreen();
}
cg.drawingHUD = CG_HUD_ZOOM;
if (cg.zoomMode == 4)
{
CG_DrawWeapReticle();
}
else if (cg.zoomMode != 0)
{
CG_DrawZoomBorders();
}
if (cg.zoomMode)
{
cg.drawingHUD = CG_HUD_NORMAL;
const auto xOffset = (-vr->off_center_fov_x * 640);
const auto yOffset = (vr->off_center_fov_y * 480);
vec4_t color = { 0, 0, 0, 1 };
if (cg.stereoView == STEREO_LEFT)
{
//Left Gap
CG_FillRect(0, 0, xOffset, 480, color);
if (yOffset < 0)
{
//Bottom Gap
CG_FillRect(0, 0, 640, yOffset, color);
}
else
{
//Top Gap
CG_FillRect(0, 480 - yOffset, 640, yOffset, color);
}
}
else
{
//Right Gap
CG_FillRect(640 - xOffset, 0, xOffset, 480, color);
if (yOffset < 0)
{
//Bottom Gap
CG_FillRect(0, 0, 640, yOffset, color);
}
else
{
//Top Gap
CG_FillRect(0, 480 - yOffset, 640, yOffset, color);
}
}
}
cg.drawingHUD = CG_HUD_SCALED;
CG_DrawBatteryCharge();
// Draw this before the text so that any text won't get clipped off
if ( !in_camera )
{
cg.drawingHUD = CG_HUD_ZOOM;
CG_DrawZoomMask();
cg.drawingHUD = CG_HUD_SCALED;
}
CG_DrawScrollText();
CG_DrawCaptionText( in_camera && vr->immersive_cinematics );
if ( in_camera )
{//still draw the saber clash flare, but nothing else
cg.drawingHUD = CG_HUD_NORMAL;
CG_SaberClashFlare();
return;
}
cg.drawingHUD = CG_HUD_NORMAL;
if ( CG_RenderingFromMiscCamera())
{
// purposely doing an early out when in a misc_camera, change it if needed.
// allowing center print when in camera mode, probably just an alpha thing - dmv
CG_DrawCenterString();
return;
}
cg.drawingHUD = CG_HUD_SCALED;
// don't draw any status if dead
if ( cg.snap->ps.stats[STAT_HEALTH] > 0 )
{
CG_DrawVignette();
if ( !(cent->gent && cent->gent->s.eFlags & (EF_LOCKED_TO_WEAPON |EF_IN_ATST)))
{
CG_DrawIconBackground();
}
CG_DrawMoveSpeedIcon();
CG_DrawWeaponSelect();
if ( cg.zoomMode == 0 )
{
CG_DrawStats();
}
CG_DrawAmmoWarning();
//CROSSHAIR is now done from the crosshair ent trace
//if ( !cg.renderingThirdPerson && !cg_dynamicCrosshair.integer ) // disruptor draws it's own crosshair artwork; binocs draw nothing; third person draws its own crosshair
//{
// CG_DrawCrosshair( NULL );
//}
CG_DrawCrosshairNames();
CG_RunRocketLocking();
CG_DrawInventorySelect();
CG_DrawForceSelect();
CG_DrawPickupItem();
CG_UseIcon();
}
CG_SaberClashFlare();
float y = 0;
if (cg_drawSnapshot.integer) {
y=CG_DrawSnapshot(y);
}
if (cg_drawFPS.integer) {
y=CG_DrawFPS(y);
}
if (cg_drawTimer.integer) {
y=CG_DrawTimer(y);
}
// don't draw center string if scoreboard is up
if ( !CG_DrawScoreboard() ) {
CG_DrawCenterString();
}
if (missionInfo_Updated)
{
if (cg.predicted_player_state.pm_type != PM_DEAD)
{
// Was a objective given?
/* if ((cg_updatedDataPadForcePower.integer) || (cg_updatedDataPadObjective.integer))
{
// How long has the game been running? If within 15 seconds of starting, throw up the datapad.
if (cg.dataPadLevelStartTime>cg.time)
{
// Make it pop up
if (!in_camera)
{
cgi_SendConsoleCommand( "datapad" );
cg.dataPadLevelStartTime=cg.time; //and don't do it again this level!
}
}
}
*/
if (!cg.missionInfoFlashTime)
{
cg.missionInfoFlashTime = cg.time + cg_missionInfoFlashTime.integer;
}
if (cg.missionInfoFlashTime < cg.time) // Time's up. They didn't read it.
{
cg.missionInfoFlashTime = 0;
missionInfo_Updated = qfalse;
CG_ClearDataPadCvars();
}
cgi_SP_GetStringTextString( "INGAME_DATAPAD_UPDATED", text, sizeof(text) );
int x_pos = 0;
int offset = 0;
y_pos = (SCREEN_HEIGHT/2)+80;
if ( cg_missionInfoCentered.integer )
{
w = cgi_R_Font_StrLenPixels(text,cgs.media.qhFontSmall, FONT_SCALE);
offset = w / 2;
x_pos = SCREEN_WIDTH / 2;
}
int tempX = x_pos;
int tempY = y_pos;
cgi_R_Font_DrawString(tempX - offset, tempY, text, colorTable[CT_LTRED1], cgs.media.qhFontSmall, -1, FONT_SCALE);
if (cg_updatedDataPadForcePower1.integer)
{
y_pos += 25;
cgi_SP_GetStringTextString("INGAME_NEW_FORCE_POWER_INFO", text, sizeof(text) );
if ( cg_missionInfoCentered.integer )
{
w = cgi_R_Font_StrLenPixels(text,cgs.media.qhFontSmall, FONT_SCALE);
offset = w / 2;
x_pos = SCREEN_WIDTH / 2;
}
tempX = x_pos;
tempY = y_pos;
cgi_R_Font_DrawString(tempX - offset, tempY, text, colorTable[CT_LTRED1], cgs.media.qhFontSmall, -1, FONT_SCALE);
}
if (cg_updatedDataPadObjective.integer)
{
y_pos += 25;
cgi_SP_GetStringTextString( "INGAME_NEW_OBJECTIVE_INFO", text, sizeof(text) );
if ( cg_missionInfoCentered.integer )
{
w = cgi_R_Font_StrLenPixels(text,cgs.media.qhFontSmall, FONT_SCALE);
offset = w / 2;
x_pos = SCREEN_WIDTH / 2;
}
tempX = x_pos;
tempY = y_pos;
cgi_R_Font_DrawString(tempX - offset, tempY, text, colorTable[CT_LTRED1], cgs.media.qhFontSmall, -1, FONT_SCALE);
}
// if (cent->gent->client->sess.missionObjectivesShown<3)
// {
// CG_DrawProportionalString((SCREEN_WIDTH/2), (SCREEN_HEIGHT/2) + 20, ingame_text[IGT_MISSIONINFO_UPDATED2],
// CG_PULSE | CG_CENTER| CG_SMALLFONT, colorTable[CT_LTRED1] );
// }
}
}
cg.drawingHUD = CG_HUD_NORMAL;
}
/*
=====================
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;
}
if (!vr->item_selector) {
CG_DrawCrosshair3D(0);
CG_DrawCrosshair3D(1);
}
//FIXME: these globals done once at start of frame for various funcs
AngleVectors (cg.refdefViewAngles, vfwd, vright, vup);
VectorCopy( vfwd, vfwd_n );
VectorCopy( vright, vright_n );
VectorCopy( vup, vup_n );
VectorNormalize( vfwd_n );
VectorNormalize( vright_n );
VectorNormalize( vup_n );
vr->cgzoommode = cg.zoomMode;
switch ( stereoView ) {
case STEREO_CENTER:
separation = 0;
break;
case STEREO_LEFT:
separation = cg_worldScale.value * (-cg_stereoSeparation.value / 2);
break;
case STEREO_RIGHT:
separation = cg_worldScale.value * (cg_stereoSeparation.value / 2);
break;
default:
separation = 0;
CG_Error( "CG_DrawActive: Undefined stereoView" );
}
//Only vehicle in JK2 is the AT-ST
vr->in_vehicle = (g_entities[0].client &&
g_entities[0].client->NPC_class == CLASS_ATST);
vr->remote_npc = !Q_stricmp( "NPC", g_entities[cg.snap->ps.viewEntity].classname );
vr->remote_droid = false;
vr->remote_turret = false;
vr->emplaced_gun = ( cg_entities[cg.snap->ps.clientNum].currentState.eFlags & EF_LOCKED_TO_WEAPON );
in_misccamera = false;
if (cg.snap->ps.viewEntity) {
if (g_entities[cg.snap->ps.viewEntity].NPC_type) {
char modelName[256];
Q_strncpyz(modelName, g_entities[cg.snap->ps.viewEntity].NPC_type, sizeof modelName);
vr->remote_droid = vr->remote_npc &&
(!Q_stricmp("gonk", modelName) || !Q_stricmp("seeker", modelName) ||
!Q_stricmp("remote", modelName)
|| !Q_strncmp("r2d2", modelName, 4) ||
!Q_strncmp("r5d2", modelName, 4) || !Q_stricmp("mouse", modelName));
}
vr->remote_turret = (!Q_stricmp("misc_panel_turret",
g_entities[cg.snap->ps.viewEntity].classname));
in_misccamera = (!Q_stricmp("misc_camera", g_entities[cg.snap->ps.viewEntity].classname))
|| vr->remote_droid
|| vr->remote_turret;
}
cg.refdef.worldscale = cg_worldScale.value;
bool usingScope = (cg.zoomMode == 2 || cg.zoomMode == 4);
//Normal 1st person view angles
if (!in_camera &&
!in_misccamera &&
!vr->remote_droid &&
!vr->remote_npc &&
!usingScope &&
!cg.renderingThirdPerson)
{
VectorCopy(vr->hmdorientation, cg.refdef.viewangles);
cg.refdef.viewangles[YAW] = vr->clientviewangles[YAW] +
SHORT2ANGLE(cg.snap->ps.delta_angles[YAW]);
AnglesToAxis(cg.refdef.viewangles, cg.refdef.viewaxis);
}
//If viewing a remote turret, take the overridden FOV
if (vr->remote_turret)
{
cg.refdef.override_fov = true;
}
//Controlling an NPC that isn't a droid
if (vr->remote_npc &&
!vr->remote_droid)
{
if (vr->remote_cooldown > cg.time)
{
VectorCopy(cg.refdefViewAngles, vr->remote_angles);
vr->take_snap = true;
}
VectorCopy(vr->hmdorientation, cg.refdef.viewangles);
cg.refdef.viewangles[YAW] = vr->remote_angles[YAW] + (vr->hmdorientation[YAW] - vr->hmdorientation_snap[YAW]) + (vr->snapTurn - vr->remote_snapTurn);
AnglesToAxis(cg.refdef.viewangles, cg.refdef.viewaxis);
}
//Sniper/E11 scope
if (usingScope)
{
cg.refdef.viewangles[ROLL] = vr->hmdorientation[ROLL];
cg.refdef.viewangles[PITCH] = vr->weaponangles[ANGLES_ADJUSTED][PITCH];
cg.refdef.viewangles[YAW] = vr->clientviewangles[YAW]
+ vr->weaponangles[ANGLES_ADJUSTED][YAW] + SHORT2ANGLE(cg.snap->ps.delta_angles[YAW]);
AnglesToAxis(cg.refdef.viewangles, cg.refdef.viewaxis);
}
//Normal 3rd person view angles
if (!in_camera &&
!in_misccamera &&
cg.renderingThirdPerson)
{
VectorCopy(vr->hmdorientation, cg.refdef.viewangles);
cg.refdef.viewangles[YAW] = vr->clientviewangles[YAW] +
(vr->hmdorientation[YAW] - vr->hmdorientation_first[YAW]) +
SHORT2ANGLE(cg.snap->ps.delta_angles[YAW]);
AnglesToAxis(cg.refdef.viewangles, cg.refdef.viewaxis);
}
//Immersive cinematic sequence 6DoF
if ((in_camera && vr->immersive_cinematics) || vr->emplaced_gun || cg.renderingThirdPerson)
{
BG_ConvertFromVR(vr->hmdposition_offset, cg.refdef.vieworg, cg.refdef.vieworg);
}
// 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 && (!in_camera || vr->immersive_cinematics) && !in_misccamera && !usingScope ) {
VectorMA( cg.refdef.vieworg, -separation, cg.refdef.viewaxis[1], cg.refdef.vieworg );
}
if ( cg.zoomMode == 3 && cg.snap->ps.batteryCharge ) // doing the Light amp goggles thing
{
cgi_R_LAGoggles();
}
if (!vr->emplaced_gun && !in_misccamera && !in_camera) {
//Vertical Positional Movement
cg.refdef.vieworg[2] -= DEFAULT_PLAYER_HEIGHT;
cg.refdef.vieworg[2] += (vr->hmdposition[1] + cg_heightAdjust.value) * cg_worldScale.value;
}
// draw 3D view
cgi_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();
}