1122 lines
31 KiB
C++
1122 lines
31 KiB
C++
// Copyright (C) 2007 Id Software, Inc.
|
|
//
|
|
|
|
#include "precompiled.h"
|
|
#pragma hdrstop
|
|
|
|
#if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE )
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
#include "Player.h"
|
|
#include "PlayerView.h"
|
|
#include "rules/GameRules.h"
|
|
#include "vehicles/Transport.h"
|
|
#include "vehicles/VehicleView.h"
|
|
#include "guis/UserInterfaceLocal.h"
|
|
#include "Weapon.h"
|
|
#include "demos/DemoManager.h"
|
|
#include "script/Script_Helper.h"
|
|
#include "script/Script_ScriptObject.h"
|
|
#include "guis/GuiSurface.h"
|
|
#include "misc/WorldToScreen.h"
|
|
#include "roles/WayPointManager.h"
|
|
#include "Atmosphere.h"
|
|
#include "client/ClientEffect.h"
|
|
#include "ContentMask.h"
|
|
|
|
const int IMPULSE_DELAY = 150;
|
|
|
|
/*
|
|
==============
|
|
idPlayerView::idPlayerView
|
|
==============
|
|
*/
|
|
idPlayerView::idPlayerView( void ) {
|
|
memset( ¤tView, 0, sizeof( currentView ) );
|
|
kickFinishTime = 0;
|
|
kickAngles.Zero();
|
|
swayAngles.Zero();
|
|
lastDamageTime = 0.0f;
|
|
fadeTime = 0;
|
|
fadeRate = 0.0;
|
|
fadeFromColor.Zero();
|
|
fadeToColor.Zero();
|
|
fadeColor.Zero();
|
|
shakeAng.Zero();
|
|
shakeAxes.Identity();
|
|
lastSpectateUpdateTime = 0;
|
|
|
|
ClearEffects();
|
|
ClearRepeaterView();
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idPlayerView::~idPlayerView
|
|
==============
|
|
*/
|
|
idPlayerView::~idPlayerView( void ) {
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idPlayerView::ClearEffects
|
|
==============
|
|
*/
|
|
void idPlayerView::ClearEffects() {
|
|
lastDamageTime = 0;
|
|
|
|
kickFinishTime = 0;
|
|
|
|
fadeTime = 0;
|
|
|
|
underWaterEffect.StopDetach();
|
|
}
|
|
|
|
/*
|
|
==============
|
|
idPlayerView::DamageImpulse
|
|
|
|
LocalKickDir is the direction of force in the player's coordinate system,
|
|
which will determine the head kick direction
|
|
==============
|
|
*/
|
|
void idPlayerView::DamageImpulse( idVec3 localKickDir, const sdDeclDamage* damageDecl ) {
|
|
//
|
|
// double vision effect
|
|
//
|
|
if ( lastDamageTime > 0.0f && SEC2MS( lastDamageTime ) + IMPULSE_DELAY > gameLocal.time ) {
|
|
// keep shotgun from obliterating the view
|
|
return;
|
|
}
|
|
|
|
//
|
|
// head angle kick
|
|
//
|
|
float kickTime = damageDecl->GetKickTime();
|
|
if ( kickTime ) {
|
|
kickFinishTime = gameLocal.time + static_cast< int >( g_kickTime.GetFloat() * kickTime );
|
|
|
|
// forward / back kick will pitch view
|
|
kickAngles[0] = localKickDir[0];
|
|
|
|
// side kick will yaw view
|
|
kickAngles[1] = localKickDir[1]*0.5f;
|
|
|
|
// up / down kick will pitch view
|
|
kickAngles[0] += localKickDir[2];
|
|
|
|
// roll will come from side
|
|
kickAngles[2] = localKickDir[1];
|
|
|
|
float kickAmplitude = damageDecl->GetKickAmplitude();
|
|
if ( kickAmplitude ) {
|
|
kickAngles *= kickAmplitude;
|
|
}
|
|
}
|
|
|
|
//
|
|
// save lastDamageTime for tunnel vision accentuation
|
|
//
|
|
lastDamageTime = MS2SEC( gameLocal.time );
|
|
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idPlayerView::WeaponFireFeedback
|
|
|
|
Called when a weapon fires, generates head twitches, etc
|
|
==================
|
|
*/
|
|
void idPlayerView::WeaponFireFeedback( const weaponFeedback_t& feedback ) {
|
|
// don't shorten a damage kick in progress
|
|
if ( feedback.recoilTime != 0 && kickFinishTime < gameLocal.time ) {
|
|
kickAngles = feedback.recoilAngles;
|
|
int finish = gameLocal.time + static_cast< int >( g_kickTime.GetFloat() * feedback.recoilTime );
|
|
kickFinishTime = finish;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::SetActiveProxyView
|
|
===================
|
|
*/
|
|
void idPlayerView::SetActiveProxyView( idEntity* other, idPlayer* player ) {
|
|
if ( activeViewProxy.IsValid() ) {
|
|
activeViewProxy->GetUsableInterface()->StopActiveViewProxy();
|
|
activeViewProxy = NULL;
|
|
|
|
gameLocal.localPlayerProperties.OnActiveViewProxyChanged( NULL );
|
|
}
|
|
|
|
if ( other != NULL ) {
|
|
other->GetUsableInterface()->BecomeActiveViewProxy( player );
|
|
activeViewProxy = other;
|
|
|
|
gameLocal.localPlayerProperties.OnActiveViewProxyChanged( other );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::UpdateProxyView
|
|
===================
|
|
*/
|
|
void idPlayerView::UpdateProxyView( idPlayer* player, bool force ) {
|
|
idEntity* proxy = player != NULL ? player->GetProxyEntity() : NULL;
|
|
|
|
if ( activeViewProxy != proxy || force ) {
|
|
SetActiveProxyView( proxy, player );
|
|
}
|
|
|
|
if ( activeViewProxy.IsValid() ) {
|
|
activeViewProxy->GetUsableInterface()->UpdateProxyView( player );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::CalculateShake
|
|
===================
|
|
*/
|
|
void idPlayerView::CalculateShake( idPlayer* other ) {
|
|
idVec3 origin, matrix;
|
|
|
|
float shakeVolume = gameSoundWorld->CurrentShakeAmplitudeForPosition( gameLocal.time, other->firstPersonViewOrigin );
|
|
|
|
//
|
|
// shakeVolume should somehow be molded into an angle here
|
|
// it should be thought of as being in the range 0.0 -> 1.0, although
|
|
// since CurrentShakeAmplitudeForPosition() returns all the shake sounds
|
|
// the player can hear, it can go over 1.0 too.
|
|
//
|
|
|
|
if( shakeVolume ) {
|
|
shakeAng[0] = gameLocal.random.CRandomFloat() * shakeVolume;
|
|
shakeAng[1] = gameLocal.random.CRandomFloat() * shakeVolume;
|
|
shakeAng[2] = gameLocal.random.CRandomFloat() * shakeVolume;
|
|
shakeAxes = shakeAng.ToMat3();
|
|
} else {
|
|
shakeAng.Zero();
|
|
shakeAxes.Identity();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::ShakeAxis
|
|
===================
|
|
*/
|
|
const idMat3& idPlayerView::ShakeAxis( void ) const {
|
|
return shakeAxes;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::AngleOffset
|
|
===================
|
|
*/
|
|
idAngles idPlayerView::AngleOffset( const idPlayer* player ) const {
|
|
idAngles angles;
|
|
|
|
angles.Zero();
|
|
|
|
if ( gameLocal.time < kickFinishTime ) {
|
|
int offset = kickFinishTime - gameLocal.time;
|
|
|
|
angles = kickAngles * static_cast< float >( Square( offset ) ) * g_kickAmplitude.GetFloat();
|
|
|
|
for ( int i = 0 ; i < 3 ; i++ ) {
|
|
if ( angles[i] > 70.0f ) {
|
|
angles[i] = 70.0f;
|
|
} else if ( angles[i] < -70.0f ) {
|
|
angles[i] = -70.0f;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( player ) {
|
|
const idWeapon* weapon = player->GetWeapon();
|
|
if ( weapon ) {
|
|
weapon->SwayAngles( angles );
|
|
}
|
|
}
|
|
|
|
return angles;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idPlayerView::SkipWorldRender
|
|
==================
|
|
*/
|
|
bool idPlayerView::SkipWorldRender( void ) {
|
|
bool skipWorldRender = false;
|
|
idPlayer* localPlayer = gameLocal.GetLocalPlayer();
|
|
if ( localPlayer != NULL ) {
|
|
sdHudModule* module = NULL;
|
|
for ( module = gameLocal.localPlayerProperties.GetDrawHudModule(); module; module = module->GetDrawNode().Next() ) {
|
|
const sdUserInterfaceLocal* ui = module->GetGui();
|
|
if( ui == NULL ) {
|
|
continue;
|
|
}
|
|
if( ui->TestGUIFlag( sdUserInterfaceLocal::GUI_INHIBIT_GAME_WORLD ) ) {
|
|
skipWorldRender = true;
|
|
break;
|
|
}
|
|
}
|
|
if ( sdUserInterfaceLocal* scoreboardUI = gameLocal.GetUserInterface( gameLocal.localPlayerProperties.GetScoreBoard() ) ) {
|
|
skipWorldRender |= scoreboardUI->TestGUIFlag( sdUserInterfaceLocal::GUI_INHIBIT_GAME_WORLD );
|
|
}
|
|
}
|
|
return skipWorldRender;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idPlayerView::SingleView
|
|
==================
|
|
*/
|
|
void idPlayerView::SingleView( idPlayer* viewPlayer, const renderView_t* view ) {
|
|
|
|
// normal rendering
|
|
if ( view == NULL ) {
|
|
return;
|
|
}
|
|
|
|
bool runEffect = false;
|
|
idVec3 const &viewOrg = view->vieworg;
|
|
if ( !g_skipLocalizedPrecipitation.GetBool() && gameLocal.GetLocalPlayer() != NULL ) {
|
|
idBounds checkBounds( viewOrg );
|
|
checkBounds.ExpandSelf( 8.0f );
|
|
const idClipModel* waterModel;
|
|
int found = gameLocal.clip.FindWater( CLIP_DEBUG_PARMS checkBounds, &waterModel, 1 );
|
|
int cont = 0;
|
|
if ( found ) {
|
|
cont = gameLocal.clip.ContentsModel( CLIP_DEBUG_PARMS viewOrg, NULL, mat3_identity, CONTENTS_WATER, waterModel, waterModel->GetOrigin(), waterModel->GetAxis() );
|
|
}
|
|
|
|
runEffect = (cont & CONTENTS_WATER) ? true : false;
|
|
}
|
|
// Update the background effect
|
|
if ( runEffect ) {
|
|
underWaterEffect.GetRenderEffect().origin = viewOrg;
|
|
if ( !underWaterEffectRunning ) {
|
|
SetupEffect();
|
|
underWaterEffect.Start( gameLocal.time );
|
|
underWaterEffectRunning = true;
|
|
} else {
|
|
underWaterEffect.Update();
|
|
}
|
|
} else {
|
|
underWaterEffect.StopDetach();
|
|
underWaterEffectRunning = false;
|
|
}
|
|
|
|
|
|
if ( viewPlayer != NULL ) {
|
|
sdClientAnimated* sight = viewPlayer->GetSight();
|
|
if ( sight != NULL ) {
|
|
float fovx, fovy;
|
|
gameLocal.CalcFov( viewPlayer->GetSightFOV(), fovx, fovy );
|
|
sight->GetRenderEntity()->weaponDepthHackFOV_x = fovx;
|
|
sight->GetRenderEntity()->weaponDepthHackFOV_y = fovy;
|
|
|
|
sight->SetOrigin( view->vieworg );
|
|
sight->SetAxis( view->viewaxis );
|
|
sight->Present();
|
|
}
|
|
}
|
|
|
|
if ( !SkipWorldRender() ) {
|
|
idFrustum viewFrustum;
|
|
|
|
BuildViewFrustum( view, viewFrustum );
|
|
|
|
// emit render commands for in-world guis
|
|
DrawWorldGuis( view, viewFrustum );
|
|
|
|
gameRenderWorld->UpdatePortalOccTestView( view->viewID );
|
|
|
|
UpdateOcclusionQueries( view );
|
|
|
|
// draw the game
|
|
gameRenderWorld->RenderScene( view );
|
|
}
|
|
|
|
sdDemoManager::GetInstance().SetRenderedView( *view );
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idPlayerView::SingleView2D
|
|
==================
|
|
*/
|
|
void idPlayerView::SingleView2D( idPlayer* viewPlayer ) {
|
|
sdPostProcess* postProcess = gameLocal.localPlayerProperties.GetPostProcess();
|
|
postProcess->DrawPost();
|
|
|
|
if ( sdDemoManager::GetInstance().g_showDemoHud.GetBool() && sdDemoManager::GetInstance().InPlayBack() ) {
|
|
sdUserInterfaceLocal* ui = gameLocal.GetUserInterface( sdDemoManager::GetInstance().GetHudHandle() );
|
|
|
|
if ( ui != NULL ) {
|
|
ui->Draw();
|
|
}
|
|
}
|
|
|
|
idPlayer* localPlayer = gameLocal.GetLocalPlayer();
|
|
if ( localPlayer != NULL || gameLocal.serverIsRepeater ) {
|
|
bool drawHud;
|
|
if ( gameLocal.serverIsRepeater ) {
|
|
drawHud = true;
|
|
} else {
|
|
drawHud = !localPlayer->InhibitHud();
|
|
}
|
|
|
|
if ( viewPlayer != NULL ) {
|
|
idEntity* proxy = viewPlayer->GetProxyEntity();
|
|
if ( proxy != NULL ) {
|
|
drawHud &= !proxy->GetUsableInterface()->GetHideHud( viewPlayer );
|
|
}
|
|
}
|
|
|
|
sdHudModule* module = NULL;
|
|
for ( module = gameLocal.localPlayerProperties.GetDrawHudModule(); module; module = module->GetDrawNode().Next() ) {
|
|
if( module->ManualDraw() ) {
|
|
continue;
|
|
}
|
|
if( drawHud || !module->AllowInhibit() ) {
|
|
module->Draw();
|
|
}
|
|
}
|
|
// jrad - the scoreboard should top everything
|
|
if ( drawHud ) {
|
|
DrawScoreBoard( localPlayer );
|
|
}
|
|
}
|
|
|
|
// test a single material drawn over everything
|
|
if ( g_testPostProcess.GetString()[0] ) {
|
|
const idMaterial* mtr = declHolder.declMaterialType.LocalFind( g_testPostProcess.GetString(), false );
|
|
if ( mtr == NULL ) {
|
|
gameLocal.Printf( "Material not found.\n" );
|
|
g_testPostProcess.SetString( "" );
|
|
} else {
|
|
deviceContext->SetColor( 1.0f, 1.0f, 1.0f, 1.0f );
|
|
deviceContext->DrawRect( 0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f, 1.0f, mtr );
|
|
}
|
|
}
|
|
|
|
if ( !g_skipViewEffects.GetBool() ) {
|
|
ScreenFade();
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idPlayerView::Flash
|
|
|
|
flashes the player view with the given color
|
|
=================
|
|
*/
|
|
void idPlayerView::Flash( const idVec4& color, int time ) {
|
|
Fade( idVec4( 0.0f, 0.0f, 0.0f, 0.0f ), time );
|
|
fadeFromColor = color;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idPlayerView::Fade
|
|
|
|
used for level transition fades
|
|
assumes: color.w is 0 or 1
|
|
=================
|
|
*/
|
|
void idPlayerView::Fade( const idVec4& color, int time ) {
|
|
|
|
if ( !fadeTime ) {
|
|
fadeFromColor.Set( 0.0f, 0.0f, 0.0f, 1.0f - color[ 3 ] );
|
|
} else {
|
|
fadeFromColor = fadeColor;
|
|
}
|
|
fadeToColor = color;
|
|
|
|
if ( time <= 0 ) {
|
|
fadeRate = 0;
|
|
time = 0;
|
|
fadeColor = fadeToColor;
|
|
} else {
|
|
fadeRate = 1.0f / ( float )time;
|
|
}
|
|
|
|
if ( gameLocal.realClientTime == 0 && time == 0 ) {
|
|
fadeTime = 1;
|
|
} else {
|
|
fadeTime = gameLocal.realClientTime + time;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idPlayerView::ScreenFade
|
|
=================
|
|
*/
|
|
void idPlayerView::ScreenFade() {
|
|
int msec;
|
|
float t;
|
|
|
|
if ( !fadeTime ) {
|
|
return;
|
|
}
|
|
|
|
msec = fadeTime - gameLocal.realClientTime;
|
|
|
|
if ( msec <= 0 ) {
|
|
fadeColor = fadeToColor;
|
|
if ( fadeColor[ 3 ] == 0.0f ) {
|
|
fadeTime = 0;
|
|
}
|
|
} else {
|
|
t = ( float )msec * fadeRate;
|
|
fadeColor = fadeFromColor * t + fadeToColor * ( 1.0f - t );
|
|
}
|
|
|
|
if ( fadeColor[ 3 ] != 0.0f ) {
|
|
deviceContext->SetColor( fadeColor[ 0 ], fadeColor[ 1 ], fadeColor[ 2 ], fadeColor[ 3 ] );
|
|
float aspectRatio = deviceContext->GetAspectRatioCorrection();
|
|
deviceContext->DrawRect( 0, 0, SCREEN_WIDTH * 1.0f / aspectRatio, SCREEN_HEIGHT, 0.0f, 0.0f, 1.0f, 1.0f, declHolder.declMaterialType.LocalFind( "_white" ) );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idPlayerView::DrawScoreBoard
|
|
================
|
|
*/
|
|
void idPlayerView::DrawScoreBoard( idPlayer* player ) {
|
|
sdUserInterfaceLocal* scoreboardUI = gameLocal.GetUserInterface( gameLocal.localPlayerProperties.GetScoreBoard() );
|
|
if ( scoreboardUI == NULL ) {
|
|
return;
|
|
}
|
|
|
|
bool skipWorldRender = SkipWorldRender();
|
|
|
|
bool showScores;
|
|
if ( sdDemoManager::GetInstance().InPlayBack() ) {
|
|
showScores = sdDemoManager::GetInstance().GetDemoCommand().clientButtons.showScores;
|
|
} else if ( player != NULL ) {
|
|
showScores = player->usercmd.clientButtons.showScores;
|
|
} else if ( gameLocal.serverIsRepeater ) {
|
|
showScores = gameLocal.playerView.GetRepeaterUserCmd().clientButtons.showScores;
|
|
} else {
|
|
showScores = false;
|
|
}
|
|
|
|
if ( ( showScores && !skipWorldRender ) || gameLocal.localPlayerProperties.ShouldShowEndGame() ) {
|
|
if( !scoreboardUI->IsActive() ) {
|
|
scoreboardUI->Activate();
|
|
if( gameLocal.localPlayerProperties.ShouldShowEndGame() ) {
|
|
sdChatMenu* menu = gameLocal.localPlayerProperties.GetChatMenu();
|
|
if( menu->Enabled() ) {
|
|
menu->Enable( false );
|
|
}
|
|
sdLimboMenu* limboMenu = gameLocal.localPlayerProperties.GetLimboMenu();
|
|
if ( limboMenu->Enabled() ) {
|
|
limboMenu->Enable( false );
|
|
}
|
|
}
|
|
}
|
|
scoreboardUI->Draw();
|
|
|
|
if( gameLocal.localPlayerProperties.ShouldShowEndGame() ) {
|
|
gameLocal.OnEndGameScoreboardActive();
|
|
}
|
|
} else if( scoreboardUI->IsActive() ){
|
|
scoreboardUI->Deactivate();
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::ClearRepeaterView
|
|
===================
|
|
*/
|
|
void idPlayerView::ClearRepeaterView( void ) {
|
|
repeaterViewInfo.deltaViewAngles.Zero();
|
|
repeaterViewInfo.viewAngles.Zero();
|
|
repeaterViewInfo.origin.Zero();
|
|
repeaterViewInfo.velocity.Zero();
|
|
repeaterViewInfo.foundSpawn = false;
|
|
repeaterCrosshairInfo.Invalidate();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::CalcRepeaterCrosshairInfo
|
|
===================
|
|
*/
|
|
const sdCrosshairInfo& idPlayerView::CalcRepeaterCrosshairInfo( void ) {
|
|
idPlayer* viewer = gameLocal.GetActiveViewer();
|
|
|
|
float range = 8192;
|
|
float radius = 8.f;
|
|
|
|
idEntity* proxy = NULL;
|
|
if ( viewer != NULL ) {
|
|
proxy = viewer->GetProxyEntity();
|
|
}
|
|
if ( proxy != NULL ) {
|
|
proxy->DisableClip( false );
|
|
}
|
|
|
|
idVec3 vieworg = repeaterViewInfo.origin;
|
|
idMat3 viewaxes = repeaterViewInfo.viewAngles.ToMat3();
|
|
|
|
trace_t trace;
|
|
memset( &trace, 0, sizeof( trace ) );
|
|
idVec3 end = vieworg + ( viewaxes[ 0 ] * range );
|
|
|
|
bool retVal = gameLocal.clip.TracePoint( CLIP_DEBUG_PARMS trace, vieworg, end, MASK_SHOT_RENDERMODEL | CONTENTS_SHADOWCOLLISION | CONTENTS_SLIDEMOVER | CONTENTS_BODY | CONTENTS_PROJECTILE | CONTENTS_CROSSHAIRSOLID, viewer, TM_CROSSHAIR_INFO, radius );
|
|
|
|
// set up approximate distance
|
|
if ( trace.c.material != NULL && trace.c.material->GetSurfaceFlags() & SURF_NOIMPACT ) {
|
|
repeaterCrosshairInfo.SetDistance( range );
|
|
} else {
|
|
repeaterCrosshairInfo.SetDistance( range * trace.fraction );
|
|
}
|
|
|
|
if ( retVal ) {
|
|
if ( trace.c.entityNum != ENTITYNUM_NONE && trace.c.entityNum != ENTITYNUM_WORLD ) {
|
|
|
|
idEntity* traceEnt = gameLocal.entities[ trace.c.entityNum ];
|
|
|
|
if ( repeaterCrosshairInfo.GetEntity() != traceEnt || !repeaterCrosshairInfo.IsValid() ) {
|
|
repeaterCrosshairInfo.SetEntity( traceEnt );
|
|
repeaterCrosshairInfo.SetStartTime( gameLocal.time );
|
|
}
|
|
|
|
if ( traceEnt != NULL ) {
|
|
if ( traceEnt->UpdateCrosshairInfo( viewer, repeaterCrosshairInfo ) ) {
|
|
repeaterCrosshairInfo.Validate();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( proxy != NULL ) {
|
|
proxy->EnableClip();
|
|
}
|
|
|
|
repeaterCrosshairInfo.SetTrace( trace );
|
|
return repeaterCrosshairInfo;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::SetRepeaterUserCmd
|
|
===================
|
|
*/
|
|
void idPlayerView::SetRepeaterUserCmd( const usercmd_t& usercmd ) {
|
|
if ( ( usercmd.buttons.btn.altAttack ) != ( repeaterUserCmd.buttons.btn.altAttack ) ) {
|
|
if ( usercmd.buttons.btn.altAttack ) {
|
|
gameLocal.ChangeLocalSpectateClient( -1 );
|
|
}
|
|
}
|
|
if ( ( usercmd.buttons.btn.attack ) != ( repeaterUserCmd.buttons.btn.attack ) ) {
|
|
if ( usercmd.buttons.btn.attack ) {
|
|
int followIndex = gameLocal.GetRepeaterFollowClientIndex();
|
|
|
|
int index = followIndex;
|
|
int count = MAX_ASYNC_CLIENTS;
|
|
while ( count > 0 ) {
|
|
count--;
|
|
index++;
|
|
if ( index >= MAX_ASYNC_CLIENTS ) {
|
|
index = 0;
|
|
}
|
|
|
|
if ( gameLocal.GetClient( index ) != NULL ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( count != 0 ) {
|
|
if ( index != followIndex ) {
|
|
gameLocal.ChangeLocalSpectateClient( index );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
repeaterUserCmd = usercmd;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::UpdateRepeaterView
|
|
===================
|
|
*/
|
|
void idPlayerView::UpdateRepeaterView( void ) {
|
|
repeaterUserOrigin_t userOrigin;
|
|
userOrigin.followClient = gameLocal.GetRepeaterFollowClientIndex();
|
|
|
|
if ( userOrigin.followClient != -1 ) {
|
|
idPlayer* player = gameLocal.GetClient( userOrigin.followClient );
|
|
assert( player != NULL );
|
|
|
|
renderView_t* view = player->GetRenderView();
|
|
|
|
repeaterViewInfo.origin = view->vieworg;
|
|
repeaterViewInfo.velocity.Zero();
|
|
repeaterViewInfo.viewAngles = view->viewaxis.ToAngles();
|
|
} else {
|
|
// update camera origin
|
|
for( int i = 0; i < 3; i++ ) {
|
|
repeaterViewInfo.viewAngles[ i ] = idMath::AngleNormalize180( SHORT2ANGLE( repeaterUserCmd.angles[ i ] ) + repeaterViewInfo.deltaViewAngles[ i ] );
|
|
}
|
|
|
|
repeaterViewInfo.viewAngles.pitch = idMath::ClampFloat( -89.f, 89.f, repeaterViewInfo.viewAngles.pitch );
|
|
repeaterViewInfo.viewAngles.roll = 0.f;
|
|
|
|
if ( !repeaterViewInfo.foundSpawn ) {
|
|
if ( gameLocal.SelectInitialSpawnPointForRepeaterClient( repeaterViewInfo.origin, repeaterViewInfo.viewAngles ) ) {
|
|
repeaterViewInfo.velocity.Zero();
|
|
repeaterViewInfo.foundSpawn = true;
|
|
}
|
|
}
|
|
|
|
// calculate vectors
|
|
idVec3 gravityNormal = gameLocal.GetGravity();
|
|
gravityNormal.Normalize();
|
|
|
|
idVec3 viewForward = repeaterViewInfo.viewAngles.ToForward();
|
|
viewForward.Normalize();
|
|
idVec3 viewRight = gravityNormal.Cross( viewForward );
|
|
viewRight.Normalize();
|
|
|
|
// scale user command
|
|
int max;
|
|
float scale;
|
|
|
|
int forwardmove = repeaterUserCmd.forwardmove;
|
|
int rightmove = repeaterUserCmd.rightmove;
|
|
int upmove = repeaterUserCmd.upmove;
|
|
|
|
max = abs( forwardmove );
|
|
if ( abs( rightmove ) > max ) {
|
|
max = abs( rightmove );
|
|
}
|
|
if ( abs( upmove ) > max ) {
|
|
max = abs( upmove );
|
|
}
|
|
|
|
if ( !max ) {
|
|
scale = 0.0f;
|
|
} else {
|
|
float total = idMath::Sqrt( (float) forwardmove * forwardmove + rightmove * rightmove + upmove * upmove );
|
|
scale = pm_democamspeed.GetFloat() * max / ( 127.0f * total );
|
|
}
|
|
|
|
// move
|
|
float frametime = MS2SEC( USERCMD_MSEC );
|
|
|
|
float speed, drop, friction, newspeed, stopspeed;
|
|
float wishspeed;
|
|
idVec3 wishdir;
|
|
|
|
// friction
|
|
speed = repeaterViewInfo.velocity.Length();
|
|
if ( speed < 20.f ) {
|
|
repeaterViewInfo.velocity.Zero();
|
|
} else {
|
|
stopspeed = pm_democamspeed.GetFloat() * 0.3f;
|
|
if ( speed < stopspeed ) {
|
|
speed = stopspeed;
|
|
}
|
|
friction = 12.0f;
|
|
drop = speed * friction * frametime;
|
|
|
|
// scale the velocity
|
|
newspeed = speed - drop;
|
|
if (newspeed < 0) {
|
|
newspeed = 0;
|
|
}
|
|
|
|
repeaterViewInfo.velocity *= newspeed / speed;
|
|
}
|
|
|
|
// accelerate
|
|
wishdir = scale * (viewForward * repeaterUserCmd.forwardmove + viewRight * repeaterUserCmd.rightmove);
|
|
wishdir -= scale * gravityNormal * repeaterUserCmd.upmove;
|
|
wishspeed = wishdir.Normalize();
|
|
wishspeed *= scale;
|
|
|
|
// q2 style accelerate
|
|
float addspeed, accelspeed, currentspeed;
|
|
|
|
currentspeed = repeaterViewInfo.velocity * wishdir;
|
|
addspeed = wishspeed - currentspeed;
|
|
if ( addspeed > 0 ) {
|
|
accelspeed = 10.0f * frametime * wishspeed;
|
|
if ( accelspeed > addspeed ) {
|
|
accelspeed = addspeed;
|
|
}
|
|
|
|
repeaterViewInfo.velocity += accelspeed * wishdir;
|
|
}
|
|
|
|
// move
|
|
idVec3 velocity = repeaterViewInfo.velocity;
|
|
float timeLeft = frametime;
|
|
|
|
int count = 10;
|
|
while ( count > 0 ) {
|
|
idVec3 newOrigin = repeaterViewInfo.origin + ( timeLeft * velocity );
|
|
|
|
idBounds bounds;
|
|
idPhysics_Player::CalcSpectateBounds( bounds );
|
|
const idClipModel* clipModel = gameLocal.clip.GetTemporaryClipModel( bounds );
|
|
|
|
trace_t trace;
|
|
gameLocal.clip.TranslationWorld( trace, repeaterViewInfo.origin, newOrigin, clipModel, mat3_identity, CONTENTS_SOLID | CONTENTS_PLAYERCLIP | CONTENTS_BODY );
|
|
if ( trace.fraction != 1.f ) {
|
|
repeaterViewInfo.origin = trace.endpos;
|
|
timeLeft -= timeLeft * trace.fraction;
|
|
velocity.ProjectOntoPlane( trace.c.normal, 1.001f );
|
|
} else {
|
|
repeaterViewInfo.origin = newOrigin;
|
|
break;
|
|
}
|
|
|
|
count--;
|
|
}
|
|
}
|
|
|
|
for( int i = 0; i < 3; i++ ) {
|
|
repeaterViewInfo.deltaViewAngles[ i ] = repeaterViewInfo.viewAngles[ i ] - SHORT2ANGLE( repeaterUserCmd.angles[ i ] );
|
|
}
|
|
|
|
userOrigin.origin = repeaterViewInfo.origin;
|
|
|
|
#ifdef SD_SUPPORT_REPEATER
|
|
networkSystem->SetClientRepeaterUserOrigin( userOrigin );
|
|
#endif // SD_SUPPORT_REPEATER
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::CalculateRepeaterView
|
|
===================
|
|
*/
|
|
void idPlayerView::CalculateRepeaterView( renderView_t& view ) {
|
|
// jrad - ensure that this is running updates
|
|
sdPostProcess* postProcess = gameLocal.localPlayerProperties.GetPostProcess();
|
|
if ( postProcess != NULL && !postProcess->Enabled() ) {
|
|
postProcess->Enable( true );
|
|
}
|
|
|
|
view.viewID = 0;
|
|
view.vieworg = repeaterViewInfo.origin;
|
|
view.viewaxis = repeaterViewInfo.viewAngles.ToMat3();
|
|
view.fov_x = g_fov.GetFloat();
|
|
|
|
view.time = gameLocal.time;
|
|
|
|
view.x = 0;
|
|
view.y = 0;
|
|
view.width = SCREEN_WIDTH;
|
|
view.height = SCREEN_HEIGHT;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::SetRepeaterViewPosition
|
|
===================
|
|
*/
|
|
void idPlayerView::SetRepeaterViewPosition( const idVec3& origin, const idAngles& angles ) {
|
|
repeaterViewInfo.origin = origin;
|
|
|
|
// set the delta angle
|
|
idAngles delta;
|
|
for( int i = 0; i < 3; i++ ) {
|
|
delta[ i ] = angles[ i ] - SHORT2ANGLE( repeaterUserCmd.angles[ i ] );
|
|
}
|
|
|
|
repeaterViewInfo.deltaViewAngles = delta;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::RenderPlayerView
|
|
===================
|
|
*/
|
|
bool idPlayerView::RenderPlayerView( idPlayer* viewPlayer ) {
|
|
// hack the shake in at the very last moment, so it can't cause any consistency problems
|
|
memset( ¤tView, 0, sizeof( currentView ) );
|
|
|
|
// allow demo manager to override the view
|
|
if ( sdDemoManager::GetInstance().CalculateRenderView( ¤tView ) ) {
|
|
// place the sound origin for the player
|
|
gameSoundWorld->PlaceListener( currentView.vieworg, currentView.viewaxis, -1, gameLocal.time );
|
|
|
|
// field of view
|
|
gameLocal.CalcFov( currentView.fov_x, currentView.fov_x, currentView.fov_y );
|
|
} else {
|
|
int listenerId = -1;
|
|
if ( viewPlayer == NULL ) {
|
|
if ( gameLocal.serverIsRepeater ) {
|
|
CalculateRepeaterView( currentView );
|
|
|
|
// field of view
|
|
gameLocal.CalcFov( currentView.fov_x, currentView.fov_x, currentView.fov_y );
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
const renderView_t& view = viewPlayer->renderView;
|
|
|
|
currentView = view;
|
|
|
|
// readjust the view one frame earlier for regular game draws (again: for base draws, not for extra draws)
|
|
// extra draws do this in their own loop
|
|
if ( gameLocal.com_unlockFPS->GetBool() && !gameLocal.unlock.unlockedDraw && gameLocal.unlock.canUnlockFrames ) {
|
|
if ( g_unlock_updateViewpos.GetBool() && g_unlock_viewStyle.GetInteger() == 1 ) {
|
|
currentView.vieworg = gameLocal.unlock.originlog[ ( gameLocal.framenum + 1 ) & 1 ];
|
|
if ( viewPlayer->weapon != NULL ) {
|
|
viewPlayer->weapon->GetRenderEntity()->origin -= gameLocal.unlock.originlog[ gameLocal.framenum & 1 ] - gameLocal.unlock.originlog[ ( gameLocal.framenum + 1 ) & 1 ];
|
|
viewPlayer->weapon->BecomeActive( TH_UPDATEVISUALS );
|
|
viewPlayer->weapon->Present();
|
|
|
|
// if the weapon has a GUI bound, offset it as well
|
|
for ( rvClientEntity* cent = viewPlayer->weapon->clientEntities.Next(); cent != NULL; cent = cent->bindNode.Next() ) {
|
|
|
|
sdGuiSurface* guiSurface;
|
|
rvClientEffect* effect;
|
|
if ( ( guiSurface = cent->Cast< sdGuiSurface >() ) != NULL ) {
|
|
idVec3 o;
|
|
guiSurface->GetRenderable().GetOrigin( o );
|
|
o -= gameLocal.unlock.originlog[ gameLocal.framenum & 1 ] - gameLocal.unlock.originlog[ ( gameLocal.framenum + 1 ) & 1 ];
|
|
guiSurface->GetRenderable().SetOrigin( o );
|
|
} else if ( ( effect = cent->Cast< rvClientEffect >() ) != NULL ) {
|
|
effect->ClientUpdateView();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// shakey shake
|
|
currentView.viewaxis = currentView.viewaxis * ShakeAxis();
|
|
|
|
listenerId = viewPlayer->entityNumber + 1;
|
|
}
|
|
|
|
gameLocal.UpdateHudStats( viewPlayer );
|
|
|
|
// place the sound origin for the player
|
|
gameSoundWorld->PlaceListener( currentView.vieworg, currentView.viewaxis, listenerId, gameLocal.time );
|
|
}
|
|
|
|
// Special view mode is enabled
|
|
if ( *g_testViewSkin.GetString() != '\0' ) {
|
|
const idDeclSkin *skin = declHolder.declSkinType.LocalFind( g_testViewSkin.GetString(), false );
|
|
currentView.globalSkin = skin;
|
|
if ( !skin ) {
|
|
gameLocal.Printf( "Skin not found.\n" );
|
|
g_testViewSkin.SetString( "" );
|
|
}
|
|
} else {
|
|
if ( viewPlayer ) {
|
|
currentView.globalSkin = viewPlayer->GetViewSkin();
|
|
} else {
|
|
currentView.globalSkin = NULL;
|
|
}
|
|
}
|
|
|
|
currentView.forceClear = g_forceClear.GetBool();
|
|
|
|
if ( sdAtmosphere::currentAtmosphere == NULL ) {
|
|
currentView.clearColor.Set( 0.0f, 0.0f, 0.0f, 0.0f );
|
|
} else {
|
|
sdAtmosphere *atmos = sdAtmosphere::currentAtmosphere;
|
|
idVec3 fogcol = atmos->GetFogColor();
|
|
currentView.clearColor.Set( fogcol[0], fogcol[1], fogcol[2], 0.f );
|
|
}
|
|
|
|
SingleView( viewPlayer, ¤tView );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::RenderPlayerView2D
|
|
===================
|
|
*/
|
|
bool idPlayerView::RenderPlayerView2D( idPlayer* viewPlayer ) {
|
|
SingleView2D( viewPlayer );
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::SetupCockpit
|
|
===================
|
|
*/
|
|
void idPlayerView::SetupCockpit( const idDict& info, idEntity* vehicle ) {
|
|
ClearCockpit();
|
|
|
|
const char* cockpitDefName = info.GetString( "def_cockpit" );
|
|
const idDeclEntityDef* cockpitDef = gameLocal.declEntityDefType[ cockpitDefName ];
|
|
if ( !cockpitDef ) {
|
|
return;
|
|
}
|
|
|
|
const char* scriptObjectName = info.GetString( "scriptobject", "default" );
|
|
|
|
cockpit = new sdClientAnimated();
|
|
cockpit->Create( &cockpitDef->dict, gameLocal.program->FindTypeInfo( scriptObjectName ) );
|
|
|
|
idScriptObject* scriptObject = cockpit->GetScriptObject();
|
|
if ( scriptObject ) {
|
|
sdScriptHelper h1;
|
|
h1.Push( vehicle->GetScriptObject() );
|
|
scriptObject->CallNonBlockingScriptEvent( scriptObject->GetFunction( "OnCockpitSetup" ), h1 );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::ClearCockpit
|
|
===================
|
|
*/
|
|
void idPlayerView::ClearCockpit( void ) {
|
|
if ( cockpit.IsValid() ) {
|
|
cockpit->Dispose();
|
|
cockpit = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::BuildViewFrustum
|
|
===================
|
|
*/
|
|
void idPlayerView::BuildViewFrustum( const renderView_t* view, idFrustum& viewFrustum ) {
|
|
float dNear, dFar, dLeft, dUp;
|
|
|
|
dNear = 0.0f;
|
|
dFar = MAX_WORLD_SIZE;
|
|
dLeft = dFar * idMath::Tan( DEG2RAD( view->fov_x * 0.5f ) );
|
|
dUp = dFar * idMath::Tan( DEG2RAD( view->fov_y * 0.5f ) );
|
|
viewFrustum.SetOrigin( view->vieworg );
|
|
viewFrustum.SetAxis( view->viewaxis );
|
|
viewFrustum.SetSize( dNear, dFar, dLeft, dUp );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::DrawWorldGuis
|
|
===================
|
|
*/
|
|
void idPlayerView::DrawWorldGuis( const renderView_t* view, const idFrustum& viewFrustum ) {
|
|
pvsHandle_t handle = gameLocal.pvs.SetupCurrentPVS( view->vieworg, PVS_ALL_PORTALS_OPEN );
|
|
|
|
sdInstanceCollector< sdGuiSurface > guiSurfaces( false );
|
|
for ( int i = 0; i < guiSurfaces.Num(); i++ ) {
|
|
guiSurfaces[i]->DrawCulled( handle, viewFrustum );
|
|
}
|
|
|
|
gameLocal.pvs.FreeCurrentPVS( handle );
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::UpdateOcclusionQueries
|
|
===================
|
|
*/
|
|
void idPlayerView::UpdateOcclusionQueries( const renderView_t* view ) {
|
|
const idList< idEntity* > &ocl = gameLocal.GetOcclusionQueryList();
|
|
|
|
for ( int i = 0; i < ocl.Num(); i++ ) {
|
|
if ( ocl[i] == NULL ) {
|
|
continue;
|
|
}
|
|
|
|
int handle = ocl[i]->GetOcclusionQueryHandle();
|
|
if ( handle == -1 ) {
|
|
continue;
|
|
}
|
|
|
|
const occlusionTest_t& occDef = ocl[i]->UpdateOcclusionInfo( view->viewID );
|
|
gameRenderWorld->UpdateOcclusionTestDef( handle, &occDef );
|
|
}
|
|
}
|
|
|
|
idCVar g_spectateViewLerpScale( "g_spectateViewLerpScale", "0.7", CVAR_FLOAT | CVAR_GAME | CVAR_ARCHIVE, "Controls view smoothing for spectators", 0.2f, 1.f );
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::UpdateSpectateView
|
|
===================
|
|
*/
|
|
void idPlayerView::UpdateSpectateView( idPlayer* other ) {
|
|
const float lerpRate = g_spectateViewLerpScale.GetFloat();
|
|
|
|
if ( other != NULL ) {
|
|
if ( gameLocal.time > lastSpectateUpdateTime ) {
|
|
lastSpectateUpdateTime = gameLocal.time;
|
|
|
|
// this is a spectator view
|
|
if ( lastSpectatePlayer != other ) {
|
|
lastSpectatePlayer = other;
|
|
lastSpectateOrigin = other->firstPersonViewOrigin;
|
|
lastSpectateAxis = other->firstPersonViewAxis.ToQuat();
|
|
} else {
|
|
idVec3 diff = other->firstPersonViewOrigin - lastSpectateOrigin;
|
|
if ( diff.LengthSqr() < Square( 1024.f ) ) {
|
|
diff *= lerpRate;
|
|
other->firstPersonViewOrigin = lastSpectateOrigin + diff;
|
|
}
|
|
lastSpectateOrigin = other->firstPersonViewOrigin;
|
|
lastSpectateAxis = other->firstPersonViewAxis.ToQuat();
|
|
}
|
|
} else {
|
|
other->firstPersonViewOrigin = lastSpectateOrigin;
|
|
other->firstPersonViewAxis = lastSpectateAxis.ToMat3();
|
|
}
|
|
} else {
|
|
lastSpectatePlayer = NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idPlayerView::Init
|
|
===================
|
|
*/
|
|
void idPlayerView::Init( void ) {
|
|
lastSpectateUpdateTime = 0;
|
|
}
|
|
|
|
void idPlayerView::SetupEffect( void ) {
|
|
idPlayer* localPlayer = gameLocal.GetLocalPlayer();
|
|
|
|
renderEffect_t &renderEffect = underWaterEffect.GetRenderEffect();
|
|
renderEffect.declEffect = gameLocal.FindEffect( localPlayer->spawnArgs.GetString( "fx_underWater" ), false );
|
|
renderEffect.axis.Identity();
|
|
renderEffect.loop = true;
|
|
renderEffect.shaderParms[SHADERPARM_RED] = 1.0f;
|
|
renderEffect.shaderParms[SHADERPARM_GREEN] = 1.0f;
|
|
renderEffect.shaderParms[SHADERPARM_BLUE] = 1.0f;
|
|
renderEffect.shaderParms[SHADERPARM_ALPHA] = 1.0f;
|
|
renderEffect.shaderParms[SHADERPARM_BRIGHTNESS] = 1.0f;
|
|
|
|
underWaterEffectRunning = false;
|
|
}
|