source-engine/game/server/portal/portal_player.cpp
2022-06-20 14:17:53 +03:00

2334 lines
64 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Player for Portal.
//
//=============================================================================//
#include "cbase.h"
#include "portal_player.h"
#include "globalstate.h"
#include "trains.h"
#include "game.h"
#include "portal_player_shared.h"
#include "predicted_viewmodel.h"
#include "in_buttons.h"
#include "portal_gamerules.h"
#include "weapon_portalgun.h"
#include "portal/weapon_physcannon.h"
#include "KeyValues.h"
#include "team.h"
#include "eventqueue.h"
#include "weapon_portalbase.h"
#include "engine/IEngineSound.h"
#include "ai_basenpc.h"
#include "SoundEmitterSystem/isoundemittersystembase.h"
#include "prop_portal_shared.h"
#include "player_pickup.h" // for player pickup code
#include "vphysics/player_controller.h"
#include "datacache/imdlcache.h"
#include "bone_setup.h"
#include "portal_gamestats.h"
#include "physicsshadowclone.h"
#include "physics_prop_ragdoll.h"
#include "soundenvelope.h"
#include "ai_speech.h" // For expressors, vcd playing
#include "sceneentity.h" // has the VCD precache function
// Max mass the player can lift with +use
#define PORTAL_PLAYER_MAX_LIFT_MASS 85
#define PORTAL_PLAYER_MAX_LIFT_SIZE 128
extern CBaseEntity *g_pLastSpawn;
extern void respawn(CBaseEntity *pEdict, bool fCopyCorpse);
// -------------------------------------------------------------------------------- //
// Player animation event. Sent to the client when a player fires, jumps, reloads, etc..
// -------------------------------------------------------------------------------- //
class CTEPlayerAnimEvent : public CBaseTempEntity
{
public:
DECLARE_CLASS( CTEPlayerAnimEvent, CBaseTempEntity );
DECLARE_SERVERCLASS();
CTEPlayerAnimEvent( const char *name ) : CBaseTempEntity( name )
{
}
CNetworkHandle( CBasePlayer, m_hPlayer );
CNetworkVar( int, m_iEvent );
CNetworkVar( int, m_nData );
};
IMPLEMENT_SERVERCLASS_ST_NOBASE( CTEPlayerAnimEvent, DT_TEPlayerAnimEvent )
SendPropEHandle( SENDINFO( m_hPlayer ) ),
SendPropInt( SENDINFO( m_iEvent ), Q_log2( PLAYERANIMEVENT_COUNT ) + 1, SPROP_UNSIGNED ),
SendPropInt( SENDINFO( m_nData ), 32 ),
END_SEND_TABLE()
static CTEPlayerAnimEvent g_TEPlayerAnimEvent( "PlayerAnimEvent" );
void TE_PlayerAnimEvent( CBasePlayer *pPlayer, PlayerAnimEvent_t event, int nData )
{
CPVSFilter filter( (const Vector&)pPlayer->EyePosition() );
g_TEPlayerAnimEvent.m_hPlayer = pPlayer;
g_TEPlayerAnimEvent.m_iEvent = event;
g_TEPlayerAnimEvent.m_nData = nData;
g_TEPlayerAnimEvent.Create( filter, 0 );
}
//=================================================================================
//
// Ragdoll Entity
//
class CPortalRagdoll : public CBaseAnimatingOverlay, public CDefaultPlayerPickupVPhysics
{
public:
DECLARE_CLASS( CPortalRagdoll, CBaseAnimatingOverlay );
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
CPortalRagdoll()
{
m_hPlayer.Set( NULL );
m_vecRagdollOrigin.Init();
m_vecRagdollVelocity.Init();
}
// Transmit ragdolls to everyone.
virtual int UpdateTransmitState()
{
return SetTransmitState( FL_EDICT_ALWAYS );
}
// In case the client has the player entity, we transmit the player index.
// In case the client doesn't have it, we transmit the player's model index, origin, and angles
// so they can create a ragdoll in the right place.
CNetworkHandle( CBaseEntity, m_hPlayer ); // networked entity handle
CNetworkVector( m_vecRagdollVelocity );
CNetworkVector( m_vecRagdollOrigin );
};
LINK_ENTITY_TO_CLASS( portal_ragdoll, CPortalRagdoll );
IMPLEMENT_SERVERCLASS_ST_NOBASE( CPortalRagdoll, DT_PortalRagdoll )
SendPropVector( SENDINFO(m_vecRagdollOrigin), -1, SPROP_COORD ),
SendPropEHandle( SENDINFO( m_hPlayer ) ),
SendPropModelIndex( SENDINFO( m_nModelIndex ) ),
SendPropInt ( SENDINFO(m_nForceBone), 8, 0 ),
SendPropVector ( SENDINFO(m_vecForce), -1, SPROP_NOSCALE ),
SendPropVector( SENDINFO( m_vecRagdollVelocity ) ),
END_SEND_TABLE()
BEGIN_DATADESC( CPortalRagdoll )
DEFINE_FIELD( m_vecRagdollOrigin, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_hPlayer, FIELD_EHANDLE ),
DEFINE_FIELD( m_vecRagdollVelocity, FIELD_VECTOR ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( player, CPortal_Player );
IMPLEMENT_SERVERCLASS_ST(CPortal_Player, DT_Portal_Player)
SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ),
SendPropExclude( "DT_BaseAnimating", "m_nSequence" ),
SendPropExclude( "DT_BaseAnimating", "m_nNewSequenceParity" ),
SendPropExclude( "DT_BaseAnimating", "m_nResetEventsParity" ),
SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ),
SendPropExclude( "DT_BaseFlex", "m_viewtarget" ),
SendPropExclude( "DT_BaseFlex", "m_flexWeight" ),
SendPropExclude( "DT_BaseFlex", "m_blinktoggle" ),
// portal_playeranimstate and clientside animation takes care of these on the client
SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ),
SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 0), 11, SPROP_CHANGES_OFTEN ),
SendPropAngle( SENDINFO_VECTORELEM(m_angEyeAngles, 1), 11, SPROP_CHANGES_OFTEN ),
SendPropEHandle( SENDINFO( m_hRagdoll ) ),
SendPropInt( SENDINFO( m_iSpawnInterpCounter), 4 ),
SendPropInt( SENDINFO( m_iPlayerSoundType), 3 ),
SendPropBool( SENDINFO( m_bHeldObjectOnOppositeSideOfPortal) ),
SendPropEHandle( SENDINFO( m_pHeldObjectPortal ) ),
SendPropBool( SENDINFO( m_bPitchReorientation ) ),
SendPropEHandle( SENDINFO( m_hPortalEnvironment ) ),
SendPropEHandle( SENDINFO( m_hSurroundingLiquidPortal ) ),
SendPropBool( SENDINFO( m_bSuppressingCrosshair ) ),
SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
END_SEND_TABLE()
BEGIN_DATADESC( CPortal_Player )
DEFINE_SOUNDPATCH( m_pWooshSound ),
DEFINE_FIELD( m_bHeldObjectOnOppositeSideOfPortal, FIELD_BOOLEAN ),
DEFINE_FIELD( m_pHeldObjectPortal, FIELD_EHANDLE ),
DEFINE_FIELD( m_bIntersectingPortalPlane, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bStuckOnPortalCollisionObject, FIELD_BOOLEAN ),
DEFINE_FIELD( m_fTimeLastHurt, FIELD_TIME ),
DEFINE_FIELD( m_StatsThisLevel.iNumPortalsPlaced, FIELD_INTEGER ),
DEFINE_FIELD( m_StatsThisLevel.iNumStepsTaken, FIELD_INTEGER ),
DEFINE_FIELD( m_StatsThisLevel.fNumSecondsTaken, FIELD_FLOAT ),
DEFINE_FIELD( m_fTimeLastNumSecondsUpdate, FIELD_TIME ),
DEFINE_FIELD( m_iNumCamerasDetatched, FIELD_INTEGER ),
DEFINE_FIELD( m_bPitchReorientation, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bIsRegenerating, FIELD_BOOLEAN ),
DEFINE_FIELD( m_fNeuroToxinDamageTime, FIELD_TIME ),
DEFINE_FIELD( m_hPortalEnvironment, FIELD_EHANDLE ),
DEFINE_FIELD( m_flExpressionLoopTime, FIELD_TIME ),
DEFINE_FIELD( m_iszExpressionScene, FIELD_STRING ),
DEFINE_FIELD( m_hExpressionSceneEnt, FIELD_EHANDLE ),
DEFINE_FIELD( m_vecTotalBulletForce, FIELD_VECTOR ),
DEFINE_FIELD( m_bSilentDropAndPickup, FIELD_BOOLEAN ),
DEFINE_FIELD( m_hRagdoll, FIELD_EHANDLE ),
DEFINE_FIELD( m_angEyeAngles, FIELD_VECTOR ),
DEFINE_FIELD( m_iPlayerSoundType, FIELD_INTEGER ),
DEFINE_FIELD( m_qPrePortalledViewAngles, FIELD_VECTOR ),
DEFINE_FIELD( m_bFixEyeAnglesFromPortalling, FIELD_BOOLEAN ),
DEFINE_FIELD( m_matLastPortalled, FIELD_VMATRIX_WORLDSPACE ),
DEFINE_FIELD( m_vWorldSpaceCenterHolder, FIELD_POSITION_VECTOR ),
DEFINE_FIELD( m_hSurroundingLiquidPortal, FIELD_EHANDLE ),
DEFINE_FIELD( m_bSuppressingCrosshair, FIELD_BOOLEAN ),
//DEFINE_FIELD ( m_PlayerAnimState, CPortalPlayerAnimState ),
//DEFINE_FIELD ( m_StatsThisLevel, PortalPlayerStatistics_t ),
DEFINE_EMBEDDEDBYREF( m_pExpresser ),
END_DATADESC()
ConVar sv_regeneration_wait_time ("sv_regeneration_wait_time", "1.0", FCVAR_REPLICATED );
const char *g_pszChellModel = "models/player/chell.mdl";
const char *g_pszPlayerModel = g_pszChellModel;
#define MAX_COMBINE_MODELS 4
#define MODEL_CHANGE_INTERVAL 5.0f
#define TEAM_CHANGE_INTERVAL 5.0f
#define PORTALPLAYER_PHYSDAMAGE_SCALE 4.0f
extern ConVar sv_turbophysics;
//----------------------------------------------------
// Player Physics Shadow
//----------------------------------------------------
#define VPHYS_MAX_DISTANCE 2.0
#define VPHYS_MAX_VEL 10
#define VPHYS_MAX_DISTSQR (VPHYS_MAX_DISTANCE*VPHYS_MAX_DISTANCE)
#define VPHYS_MAX_VELSQR (VPHYS_MAX_VEL*VPHYS_MAX_VEL)
extern float IntervalDistance( float x, float x0, float x1 );
//disable 'this' : used in base member initializer list
#pragma warning( disable : 4355 )
CPortal_Player::CPortal_Player()
{
m_PlayerAnimState = CreatePortalPlayerAnimState( this );
CreateExpresser();
UseClientSideAnimation();
m_angEyeAngles.Init();
m_iLastWeaponFireUsercmd = 0;
m_iSpawnInterpCounter = 0;
m_bHeldObjectOnOppositeSideOfPortal = false;
m_pHeldObjectPortal = 0;
m_bIntersectingPortalPlane = false;
m_bPitchReorientation = false;
m_bSilentDropAndPickup = false;
m_iszExpressionScene = NULL_STRING;
m_hExpressionSceneEnt = NULL;
m_flExpressionLoopTime = 0.0f;
m_bSuppressingCrosshair = false;
}
CPortal_Player::~CPortal_Player( void )
{
ClearSceneEvents( NULL, true );
if ( m_PlayerAnimState )
m_PlayerAnimState->Release();
CPortalRagdoll *pRagdoll = dynamic_cast<CPortalRagdoll*>( m_hRagdoll.Get() );
if( pRagdoll )
{
UTIL_Remove( pRagdoll );
}
}
void CPortal_Player::UpdateOnRemove( void )
{
BaseClass::UpdateOnRemove();
}
void CPortal_Player::Precache( void )
{
BaseClass::Precache();
PrecacheScriptSound( "PortalPlayer.EnterPortal" );
PrecacheScriptSound( "PortalPlayer.ExitPortal" );
PrecacheScriptSound( "PortalPlayer.Woosh" );
PrecacheScriptSound( "PortalPlayer.FallRecover" );
PrecacheModel ( "sprites/glow01.vmt" );
//Precache Citizen models
PrecacheModel( g_pszPlayerModel );
PrecacheModel( g_pszChellModel );
PrecacheScriptSound( "NPC_Citizen.die" );
}
void CPortal_Player::CreateSounds()
{
if ( !m_pWooshSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
CPASAttenuationFilter filter( this );
m_pWooshSound = controller.SoundCreate( filter, entindex(), "PortalPlayer.Woosh" );
controller.Play( m_pWooshSound, 0, 100 );
}
}
void CPortal_Player::StopLoopingSounds()
{
if ( m_pWooshSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pWooshSound );
m_pWooshSound = NULL;
}
BaseClass::StopLoopingSounds();
}
void CPortal_Player::GiveAllItems( void )
{
EquipSuit();
CBasePlayer::GiveAmmo( 255, "Pistol");
CBasePlayer::GiveAmmo( 32, "357" );
CBasePlayer::GiveAmmo( 255, "AR2" );
CBasePlayer::GiveAmmo( 3, "AR2AltFire" );
CBasePlayer::GiveAmmo( 255, "SMG1");
CBasePlayer::GiveAmmo( 3, "smg1_grenade");
CBasePlayer::GiveAmmo( 255, "Buckshot");
CBasePlayer::GiveAmmo( 16, "XBowBolt" );
CBasePlayer::GiveAmmo( 3, "rpg_round");
CBasePlayer::GiveAmmo( 6, "grenade" );
GiveNamedItem( "weapon_crowbar" );
GiveNamedItem( "weapon_physcannon" );
GiveNamedItem( "weapon_pistol" );
GiveNamedItem( "weapon_357" );
GiveNamedItem( "weapon_smg1" );
GiveNamedItem( "weapon_ar2" );
GiveNamedItem( "weapon_shotgun" );
GiveNamedItem( "weapon_crossbow" );
GiveNamedItem( "weapon_rpg" );
GiveNamedItem( "weapon_frag" );
GiveNamedItem( "weapon_bugbait" );
//GiveNamedItem( "weapon_physcannon" );
CWeaponPortalgun *pPortalGun = static_cast<CWeaponPortalgun*>( GiveNamedItem( "weapon_portalgun" ) );
if ( !pPortalGun )
{
pPortalGun = static_cast<CWeaponPortalgun*>( Weapon_OwnsThisType( "weapon_portalgun" ) );
}
if ( pPortalGun )
{
pPortalGun->SetCanFirePortal1();
pPortalGun->SetCanFirePortal2();
}
}
void CPortal_Player::GiveDefaultItems( void )
{
castable_string_t st( "suit_no_sprint" );
GlobalEntity_SetState( st, GLOBAL_OFF );
inputdata_t in;
InputDisableFlashlight( in );
}
//-----------------------------------------------------------------------------
// Purpose: Sets specific defaults.
//-----------------------------------------------------------------------------
void CPortal_Player::Spawn(void)
{
SetPlayerModel();
BaseClass::Spawn();
CreateSounds();
pl.deadflag = false;
RemoveSolidFlags( FSOLID_NOT_SOLID );
RemoveEffects( EF_NODRAW );
StopObserverMode();
GiveDefaultItems();
m_nRenderFX = kRenderNormal;
m_Local.m_iHideHUD = 0;
AddFlag(FL_ONGROUND); // set the player on the ground at the start of the round.
m_impactEnergyScale = PORTALPLAYER_PHYSDAMAGE_SCALE;
RemoveFlag( FL_FROZEN );
m_iSpawnInterpCounter = (m_iSpawnInterpCounter + 1) % 8;
m_Local.m_bDucked = false;
SetPlayerUnderwater(false);
#ifdef PORTAL_MP
PickTeam();
#endif
}
void CPortal_Player::Activate( void )
{
BaseClass::Activate();
m_fTimeLastNumSecondsUpdate = gpGlobals->curtime;
}
void CPortal_Player::NotifySystemEvent(CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params )
{
// On teleport, we send event for tracking fling achievements
if ( eventType == NOTIFY_EVENT_TELEPORT )
{
CProp_Portal *pEnteredPortal = dynamic_cast<CProp_Portal*>( pNotify );
IGameEvent *event = gameeventmanager->CreateEvent( "portal_player_portaled" );
if ( event )
{
event->SetInt( "userid", GetUserID() );
event->SetBool( "portal2", pEnteredPortal->m_bIsPortal2 );
gameeventmanager->FireEvent( event );
}
}
BaseClass::NotifySystemEvent( pNotify, eventType, params );
}
void CPortal_Player::OnRestore( void )
{
BaseClass::OnRestore();
if ( m_pExpresser )
{
m_pExpresser->SetOuter ( this );
}
}
//bool CPortal_Player::StartObserverMode( int mode )
//{
// //Do nothing.
//
// return false;
//}
bool CPortal_Player::ValidatePlayerModel( const char *pModel )
{
if ( !Q_stricmp( g_pszPlayerModel, pModel ) )
{
return true;
}
if ( !Q_stricmp( g_pszChellModel, pModel ) )
{
return true;
}
return false;
}
void CPortal_Player::SetPlayerModel( void )
{
const char *szModelName = NULL;
const char *pszCurrentModelName = modelinfo->GetModelName( GetModel());
szModelName = engine->GetClientConVarValue( engine->IndexOfEdict( edict() ), "cl_playermodel" );
if ( ValidatePlayerModel( szModelName ) == false )
{
char szReturnString[512];
if ( ValidatePlayerModel( pszCurrentModelName ) == false )
{
pszCurrentModelName = g_pszPlayerModel;
}
Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", pszCurrentModelName );
engine->ClientCommand ( edict(), szReturnString );
szModelName = pszCurrentModelName;
}
int modelIndex = modelinfo->GetModelIndex( szModelName );
if ( modelIndex == -1 )
{
szModelName = g_pszPlayerModel;
char szReturnString[512];
Q_snprintf( szReturnString, sizeof (szReturnString ), "cl_playermodel %s\n", szModelName );
engine->ClientCommand ( edict(), szReturnString );
}
SetModel( szModelName );
m_iPlayerSoundType = (int)PLAYER_SOUNDS_CITIZEN;
}
bool CPortal_Player::Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex )
{
bool bRet = BaseClass::Weapon_Switch( pWeapon, viewmodelindex );
return bRet;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPortal_Player::UpdateExpression( void )
{
if ( !m_pExpresser )
return;
int iConcept = CONCEPT_CHELL_IDLE;
if ( GetHealth() <= 0 )
{
iConcept = CONCEPT_CHELL_DEAD;
}
GetExpresser()->SetOuter( this );
ClearExpression();
AI_Response *response = SpeakFindResponse( g_pszChellConcepts[iConcept] );
if ( !response )
{
m_flExpressionLoopTime = gpGlobals->curtime + RandomFloat(30,40);
return;
}
char szScene[256] = { 0 };
response->GetResponse( szScene, sizeof(szScene) );
// Ignore updates that choose the same scene
if ( m_iszExpressionScene != NULL_STRING && stricmp( STRING(m_iszExpressionScene), szScene ) == 0 )
return;
if ( m_hExpressionSceneEnt )
{
ClearExpression();
}
m_iszExpressionScene = AllocPooledString( szScene );
float flDuration = InstancedScriptedScene( this, szScene, &m_hExpressionSceneEnt, 0.0, true, NULL );
m_flExpressionLoopTime = gpGlobals->curtime + flDuration;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPortal_Player::ClearExpression( void )
{
if ( m_hExpressionSceneEnt != NULL )
{
StopScriptedScene( this, m_hExpressionSceneEnt );
}
m_flExpressionLoopTime = gpGlobals->curtime;
}
void CPortal_Player::PreThink( void )
{
QAngle vOldAngles = GetLocalAngles();
QAngle vTempAngles = GetLocalAngles();
vTempAngles = EyeAngles();
if ( vTempAngles[PITCH] > 180.0f )
{
vTempAngles[PITCH] -= 360.0f;
}
SetLocalAngles( vTempAngles );
BaseClass::PreThink();
if( (m_afButtonPressed & IN_JUMP) )
{
Jump();
}
//Reset bullet force accumulator, only lasts one frame
m_vecTotalBulletForce = vec3_origin;
SetLocalAngles( vOldAngles );
}
void CPortal_Player::PostThink( void )
{
BaseClass::PostThink();
// Store the eye angles pitch so the client can compute its animation state correctly.
m_angEyeAngles = EyeAngles();
QAngle angles = GetLocalAngles();
angles[PITCH] = 0;
SetLocalAngles( angles );
// Regenerate heath after 3 seconds
if ( IsAlive() && GetHealth() < GetMaxHealth() )
{
// Color to overlay on the screen while the player is taking damage
color32 hurtScreenOverlay = {64,0,0,64};
if ( gpGlobals->curtime > m_fTimeLastHurt + sv_regeneration_wait_time.GetFloat() )
{
TakeHealth( 1, DMG_GENERIC );
m_bIsRegenerating = true;
if ( GetHealth() >= GetMaxHealth() )
{
m_bIsRegenerating = false;
}
}
else
{
m_bIsRegenerating = false;
UTIL_ScreenFade( this, hurtScreenOverlay, 1.0f, 0.1f, FFADE_IN|FFADE_PURGE );
}
}
UpdatePortalPlaneSounds();
UpdateWooshSounds();
m_PlayerAnimState->Update( m_angEyeAngles[YAW], m_angEyeAngles[PITCH] );
if ( IsAlive() && m_flExpressionLoopTime >= 0 && gpGlobals->curtime > m_flExpressionLoopTime )
{
// Random expressions need to be cleared, because they don't loop. So if we
// pick the same one again, we want to restart it.
ClearExpression();
m_iszExpressionScene = NULL_STRING;
UpdateExpression();
}
UpdateSecondsTaken();
// Try to fix the player if they're stuck
if ( m_bStuckOnPortalCollisionObject )
{
Vector vForward = ((CProp_Portal*)m_hPortalEnvironment.Get())->m_vPrevForward;
Vector vNewPos = GetAbsOrigin() + vForward * gpGlobals->frametime * -1000.0f;
Teleport( &vNewPos, NULL, &vForward );
m_bStuckOnPortalCollisionObject = false;
}
}
void CPortal_Player::PlayerDeathThink(void)
{
float flForward;
SetNextThink( gpGlobals->curtime + 0.1f );
if (GetFlags() & FL_ONGROUND)
{
flForward = GetAbsVelocity().Length() - 20;
if (flForward <= 0)
{
SetAbsVelocity( vec3_origin );
}
else
{
Vector vecNewVelocity = GetAbsVelocity();
VectorNormalize( vecNewVelocity );
vecNewVelocity *= flForward;
SetAbsVelocity( vecNewVelocity );
}
}
if ( HasWeapons() )
{
// we drop the guns here because weapons that have an area effect and can kill their user
// will sometimes crash coming back from CBasePlayer::Killed() if they kill their owner because the
// player class sometimes is freed. It's safer to manipulate the weapons once we know
// we aren't calling into any of their code anymore through the player pointer.
PackDeadPlayerItems();
}
if (GetModelIndex() && (!IsSequenceFinished()) && (m_lifeState == LIFE_DYING))
{
StudioFrameAdvance( );
m_iRespawnFrames++;
if ( m_iRespawnFrames < 60 ) // animations should be no longer than this
return;
}
if (m_lifeState == LIFE_DYING)
m_lifeState = LIFE_DEAD;
StopAnimation();
IncrementInterpolationFrame();
m_flPlaybackRate = 0.0;
int fAnyButtonDown = (m_nButtons & ~IN_SCORE);
// Strip out the duck key from this check if it's toggled
if ( (fAnyButtonDown & IN_DUCK) && GetToggledDuckState())
{
fAnyButtonDown &= ~IN_DUCK;
}
// wait for all buttons released
if ( m_lifeState == LIFE_DEAD )
{
if ( fAnyButtonDown || gpGlobals->curtime < m_flDeathTime + DEATH_ANIMATION_TIME )
return;
if ( g_pGameRules->FPlayerCanRespawn( this ) )
{
m_lifeState = LIFE_RESPAWNABLE;
}
return;
}
// if the player has been dead for one second longer than allowed by forcerespawn,
// forcerespawn isn't on. Send the player off to an intermission camera until they
// choose to respawn.
if ( g_pGameRules->IsMultiplayer() && ( gpGlobals->curtime > (m_flDeathTime + DEATH_ANIMATION_TIME) ) && !IsObserver() )
{
// go to dead camera.
StartObserverMode( m_iObserverLastMode );
}
// wait for any button down, or mp_forcerespawn is set and the respawn time is up
if (!fAnyButtonDown
&& !( g_pGameRules->IsMultiplayer() && forcerespawn.GetInt() > 0 && (gpGlobals->curtime > (m_flDeathTime + 5))) )
return;
m_nButtons = 0;
m_iRespawnFrames = 0;
//Msg( "Respawn\n");
respawn( this, !IsObserver() );// don't copy a corpse if we're in deathcam.
SetNextThink( TICK_NEVER_THINK );
}
void CPortal_Player::UpdatePortalPlaneSounds( void )
{
CProp_Portal *pPortal = m_hPortalEnvironment;
if ( pPortal && pPortal->m_bActivated )
{
Vector vVelocity;
GetVelocity( &vVelocity, NULL );
if ( !vVelocity.IsZero() )
{
Vector vMin, vMax;
CollisionProp()->WorldSpaceAABB( &vMin, &vMax );
Vector vEarCenter = ( vMax + vMin ) / 2.0f;
Vector vDiagonal = vMax - vMin;
if ( !m_bIntersectingPortalPlane )
{
vDiagonal *= 0.25f;
if ( UTIL_IsBoxIntersectingPortal( vEarCenter, vDiagonal, pPortal ) )
{
m_bIntersectingPortalPlane = true;
CPASAttenuationFilter filter( this );
CSoundParameters params;
if ( GetParametersForSound( "PortalPlayer.EnterPortal", params, NULL ) )
{
EmitSound_t ep( params );
ep.m_nPitch = 80.0f + vVelocity.Length() * 0.03f;
ep.m_flVolume = MIN( 0.3f + vVelocity.Length() * 0.00075f, 1.0f );
EmitSound( filter, entindex(), ep );
}
}
}
else
{
vDiagonal *= 0.30f;
if ( !UTIL_IsBoxIntersectingPortal( vEarCenter, vDiagonal, pPortal ) )
{
m_bIntersectingPortalPlane = false;
CPASAttenuationFilter filter( this );
CSoundParameters params;
if ( GetParametersForSound( "PortalPlayer.ExitPortal", params, NULL ) )
{
EmitSound_t ep( params );
ep.m_nPitch = 80.0f + vVelocity.Length() * 0.03f;
ep.m_flVolume = MIN( 0.3f + vVelocity.Length() * 0.00075f, 1.0f );
EmitSound( filter, entindex(), ep );
}
}
}
}
}
else if ( m_bIntersectingPortalPlane )
{
m_bIntersectingPortalPlane = false;
CPASAttenuationFilter filter( this );
CSoundParameters params;
if ( GetParametersForSound( "PortalPlayer.ExitPortal", params, NULL ) )
{
EmitSound_t ep( params );
Vector vVelocity;
GetVelocity( &vVelocity, NULL );
ep.m_nPitch = 80.0f + vVelocity.Length() * 0.03f;
ep.m_flVolume = MIN( 0.3f + vVelocity.Length() * 0.00075f, 1.0f );
EmitSound( filter, entindex(), ep );
}
}
}
void CPortal_Player::UpdateWooshSounds( void )
{
if ( m_pWooshSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
float fWooshVolume = GetAbsVelocity().Length() - MIN_FLING_SPEED;
if ( fWooshVolume < 0.0f )
{
controller.SoundChangeVolume( m_pWooshSound, 0.0f, 0.1f );
return;
}
fWooshVolume /= 2000.0f;
if ( fWooshVolume > 1.0f )
fWooshVolume = 1.0f;
controller.SoundChangeVolume( m_pWooshSound, fWooshVolume, 0.1f );
// controller.SoundChangePitch( m_pWooshSound, fWooshVolume + 0.5f, 0.1f );
}
}
void CPortal_Player::FireBullets ( const FireBulletsInfo_t &info )
{
NoteWeaponFired();
BaseClass::FireBullets( info );
}
void CPortal_Player::NoteWeaponFired( void )
{
Assert( m_pCurrentCommand );
if( m_pCurrentCommand )
{
m_iLastWeaponFireUsercmd = m_pCurrentCommand->command_number;
}
}
extern ConVar sv_maxunlag;
bool CPortal_Player::WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const
{
// No need to lag compensate at all if we're not attacking in this command and
// we haven't attacked recently.
if ( !( pCmd->buttons & IN_ATTACK ) && (pCmd->command_number - m_iLastWeaponFireUsercmd > 5) )
return false;
// If this entity hasn't been transmitted to us and acked, then don't bother lag compensating it.
if ( pEntityTransmitBits && !pEntityTransmitBits->Get( pPlayer->entindex() ) )
return false;
const Vector &vMyOrigin = GetAbsOrigin();
const Vector &vHisOrigin = pPlayer->GetAbsOrigin();
// get max distance player could have moved within max lag compensation time,
// multiply by 1.5 to to avoid "dead zones" (sqrt(2) would be the exact value)
float maxDistance = 1.5 * pPlayer->MaxSpeed() * sv_maxunlag.GetFloat();
// If the player is within this distance, lag compensate them in case they're running past us.
if ( vHisOrigin.DistTo( vMyOrigin ) < maxDistance )
return true;
// If their origin is not within a 45 degree cone in front of us, no need to lag compensate.
Vector vForward;
AngleVectors( pCmd->viewangles, &vForward );
Vector vDiff = vHisOrigin - vMyOrigin;
VectorNormalize( vDiff );
float flCosAngle = 0.707107f; // 45 degree angle
if ( vForward.Dot( vDiff ) < flCosAngle )
return false;
return true;
}
void CPortal_Player::DoAnimationEvent( PlayerAnimEvent_t event, int nData )
{
m_PlayerAnimState->DoAnimationEvent( event, nData );
TE_PlayerAnimEvent( this, event, nData ); // Send to any clients who can see this guy.
}
//-----------------------------------------------------------------------------
// Purpose: Override setup bones so that is uses the render angles from
// the Portal animation state to setup the hitboxes.
//-----------------------------------------------------------------------------
void CPortal_Player::SetupBones( matrix3x4_t *pBoneToWorld, int boneMask )
{
VPROF_BUDGET( "CBaseAnimating::SetupBones", VPROF_BUDGETGROUP_SERVER_ANIM );
// Set the mdl cache semaphore.
MDLCACHE_CRITICAL_SECTION();
// Get the studio header.
Assert( GetModelPtr() );
CStudioHdr *pStudioHdr = GetModelPtr( );
Vector pos[MAXSTUDIOBONES];
Quaternion q[MAXSTUDIOBONES];
// Adjust hit boxes based on IK driven offset.
Vector adjOrigin = GetAbsOrigin() + Vector( 0, 0, m_flEstIkOffset );
// FIXME: pass this into Studio_BuildMatrices to skip transforms
CBoneBitList boneComputed;
if ( m_pIk )
{
m_iIKCounter++;
m_pIk->Init( pStudioHdr, GetAbsAngles(), adjOrigin, gpGlobals->curtime, m_iIKCounter, boneMask );
GetSkeleton( pStudioHdr, pos, q, boneMask );
m_pIk->UpdateTargets( pos, q, pBoneToWorld, boneComputed );
CalculateIKLocks( gpGlobals->curtime );
m_pIk->SolveDependencies( pos, q, pBoneToWorld, boneComputed );
}
else
{
GetSkeleton( pStudioHdr, pos, q, boneMask );
}
CBaseAnimating *pParent = dynamic_cast< CBaseAnimating* >( GetMoveParent() );
if ( pParent )
{
// We're doing bone merging, so do special stuff here.
CBoneCache *pParentCache = pParent->GetBoneCache();
if ( pParentCache )
{
BuildMatricesWithBoneMerge(
pStudioHdr,
m_PlayerAnimState->GetRenderAngles(),
adjOrigin,
pos,
q,
pBoneToWorld,
pParent,
pParentCache );
return;
}
}
Studio_BuildMatrices(
pStudioHdr,
m_PlayerAnimState->GetRenderAngles(),
adjOrigin,
pos,
q,
-1,
GetModelScale(), // Scaling
pBoneToWorld,
boneMask );
}
// Set the activity based on an event or current state
void CPortal_Player::SetAnimation( PLAYER_ANIM playerAnim )
{
return;
}
CAI_Expresser *CPortal_Player::CreateExpresser()
{
Assert( !m_pExpresser );
if ( m_pExpresser )
{
delete m_pExpresser;
}
m_pExpresser = new CAI_Expresser(this);
if ( !m_pExpresser)
{
return NULL;
}
m_pExpresser->Connect(this);
return m_pExpresser;
}
//-----------------------------------------------------------------------------
CAI_Expresser *CPortal_Player::GetExpresser()
{
if ( m_pExpresser )
{
m_pExpresser->Connect(this);
}
return m_pExpresser;
}
extern int gEvilImpulse101;
//-----------------------------------------------------------------------------
// Purpose: Player reacts to bumping a weapon.
// Input : pWeapon - the weapon that the player bumped into.
// Output : Returns true if player picked up the weapon
//-----------------------------------------------------------------------------
bool CPortal_Player::BumpWeapon( CBaseCombatWeapon *pWeapon )
{
CBaseCombatCharacter *pOwner = pWeapon->GetOwner();
// Can I have this weapon type?
if ( !IsAllowedToPickupWeapons() )
return false;
if ( pOwner || !Weapon_CanUse( pWeapon ) || !g_pGameRules->CanHavePlayerItem( this, pWeapon ) )
{
if ( gEvilImpulse101 )
{
UTIL_Remove( pWeapon );
}
return false;
}
// Don't let the player fetch weapons through walls (use MASK_SOLID so that you can't pickup through windows)
if( !pWeapon->FVisible( this, MASK_SOLID ) && !(GetFlags() & FL_NOTARGET) )
{
return false;
}
CWeaponPortalgun *pPickupPortalgun = dynamic_cast<CWeaponPortalgun*>( pWeapon );
bool bOwnsWeaponAlready = !!Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType());
if ( bOwnsWeaponAlready == true )
{
// If we picked up a second portal gun set the bool to alow secondary fire
if ( pPickupPortalgun )
{
CWeaponPortalgun *pPortalGun = static_cast<CWeaponPortalgun*>( Weapon_OwnsThisType( pWeapon->GetClassname() ) );
if ( pPickupPortalgun->CanFirePortal1() )
pPortalGun->SetCanFirePortal1();
if ( pPickupPortalgun->CanFirePortal2() )
pPortalGun->SetCanFirePortal2();
UTIL_Remove( pWeapon );
return true;
}
//If we have room for the ammo, then "take" the weapon too.
if ( Weapon_EquipAmmoOnly( pWeapon ) )
{
pWeapon->CheckRespawn();
UTIL_Remove( pWeapon );
return true;
}
else
{
return false;
}
}
pWeapon->CheckRespawn();
Weapon_Equip( pWeapon );
// If we're holding and object before picking up portalgun, drop it
if ( pPickupPortalgun )
{
ForceDropOfCarriedPhysObjects( GetPlayerHeldEntity( this ) );
}
return true;
}
void CPortal_Player::ShutdownUseEntity( void )
{
ShutdownPickupController( m_hUseEntity );
}
const Vector& CPortal_Player::WorldSpaceCenter( ) const
{
m_vWorldSpaceCenterHolder = GetAbsOrigin();
m_vWorldSpaceCenterHolder.z += ( (IsDucked()) ? (VEC_DUCK_HULL_MAX_SCALED( this ).z) : (VEC_HULL_MAX_SCALED( this ).z) ) * 0.5f;
return m_vWorldSpaceCenterHolder;
}
void CPortal_Player::Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity )
{
Vector oldOrigin = GetLocalOrigin();
QAngle oldAngles = GetLocalAngles();
BaseClass::Teleport( newPosition, newAngles, newVelocity );
m_angEyeAngles = pl.v_angle;
m_PlayerAnimState->Teleport( newPosition, newAngles, this );
}
void CPortal_Player::VPhysicsShadowUpdate( IPhysicsObject *pPhysics )
{
if( m_hPortalEnvironment.Get() == NULL )
return BaseClass::VPhysicsShadowUpdate( pPhysics );
//below is mostly a cut/paste of existing CBasePlayer::VPhysicsShadowUpdate code with some minor tweaks to avoid getting stuck in stuff when in a portal environment
if ( sv_turbophysics.GetBool() )
return;
Vector newPosition;
bool physicsUpdated = m_pPhysicsController->GetShadowPosition( &newPosition, NULL ) > 0 ? true : false;
// UNDONE: If the player is penetrating, but the player's game collisions are not stuck, teleport the physics shadow to the game position
if ( pPhysics->GetGameFlags() & FVPHYSICS_PENETRATING )
{
CUtlVector<CBaseEntity *> list;
PhysGetListOfPenetratingEntities( this, list );
for ( int i = list.Count()-1; i >= 0; --i )
{
// filter out anything that isn't simulated by vphysics
// UNDONE: Filter out motion disabled objects?
if ( list[i]->GetMoveType() == MOVETYPE_VPHYSICS )
{
// I'm currently stuck inside a moving object, so allow vphysics to
// apply velocity to the player in order to separate these objects
m_touchedPhysObject = true;
}
}
}
if ( m_pPhysicsController->IsInContact() || (m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER) )
{
m_touchedPhysObject = true;
}
if ( IsFollowingPhysics() )
{
m_touchedPhysObject = true;
}
if ( GetMoveType() == MOVETYPE_NOCLIP )
{
m_oldOrigin = GetAbsOrigin();
return;
}
if ( phys_timescale.GetFloat() == 0.0f )
{
physicsUpdated = false;
}
if ( !physicsUpdated )
return;
IPhysicsObject *pPhysGround = GetGroundVPhysics();
Vector newVelocity;
pPhysics->GetPosition( &newPosition, 0 );
m_pPhysicsController->GetShadowVelocity( &newVelocity );
Vector tmp = GetAbsOrigin() - newPosition;
if ( !m_touchedPhysObject && !(GetFlags() & FL_ONGROUND) )
{
tmp.z *= 0.5f; // don't care about z delta as much
}
float dist = tmp.LengthSqr();
float deltaV = (newVelocity - GetAbsVelocity()).LengthSqr();
float maxDistErrorSqr = VPHYS_MAX_DISTSQR;
float maxVelErrorSqr = VPHYS_MAX_VELSQR;
if ( IsRideablePhysics(pPhysGround) )
{
maxDistErrorSqr *= 0.25;
maxVelErrorSqr *= 0.25;
}
if ( dist >= maxDistErrorSqr || deltaV >= maxVelErrorSqr || (pPhysGround && !m_touchedPhysObject) )
{
if ( m_touchedPhysObject || pPhysGround )
{
// BUGBUG: Rewrite this code using fixed timestep
if ( deltaV >= maxVelErrorSqr )
{
Vector dir = GetAbsVelocity();
float len = VectorNormalize(dir);
float dot = DotProduct( newVelocity, dir );
if ( dot > len )
{
dot = len;
}
else if ( dot < -len )
{
dot = -len;
}
VectorMA( newVelocity, -dot, dir, newVelocity );
if ( m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER )
{
float val = Lerp( 0.1f, len, dot );
VectorMA( newVelocity, val - len, dir, newVelocity );
}
if ( !IsRideablePhysics(pPhysGround) )
{
if ( !(m_afPhysicsFlags & PFLAG_VPHYSICS_MOTIONCONTROLLER ) && IsSimulatingOnAlternateTicks() )
{
newVelocity *= 0.5f;
}
ApplyAbsVelocityImpulse( newVelocity );
}
}
trace_t trace;
UTIL_TraceEntity( this, newPosition, newPosition, MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace );
if ( !trace.allsolid && !trace.startsolid )
{
SetAbsOrigin( newPosition );
}
}
else
{
trace_t trace;
Ray_t ray;
ray.Init( GetAbsOrigin(), GetAbsOrigin(), WorldAlignMins(), WorldAlignMaxs() );
CTraceFilterSimple OriginalTraceFilter( this, COLLISION_GROUP_PLAYER_MOVEMENT );
CTraceFilterTranslateClones traceFilter( &OriginalTraceFilter );
UTIL_Portal_TraceRay_With( m_hPortalEnvironment, ray, MASK_PLAYERSOLID, &traceFilter, &trace );
// current position is not ok, fixup
if ( trace.allsolid || trace.startsolid )
{
//try again with new position
ray.Init( newPosition, newPosition, WorldAlignMins(), WorldAlignMaxs() );
UTIL_Portal_TraceRay_With( m_hPortalEnvironment, ray, MASK_PLAYERSOLID, &traceFilter, &trace );
if( trace.startsolid == false )
{
SetAbsOrigin( newPosition );
}
else
{
if( !FindClosestPassableSpace( this, newPosition - GetAbsOrigin(), MASK_PLAYERSOLID ) )
{
// Try moving the player closer to the center of the portal
CProp_Portal *pPortal = m_hPortalEnvironment.Get();
newPosition += ( pPortal->GetAbsOrigin() - WorldSpaceCenter() ) * 0.1f;
SetAbsOrigin( newPosition );
DevMsg( "Hurting the player for FindClosestPassableSpaceFailure!" );
// Deal 1 damage per frame... this will kill a player very fast, but allow for the above correction to fix some cases
CTakeDamageInfo info( this, this, vec3_origin, vec3_origin, 1, DMG_CRUSH );
OnTakeDamage( info );
}
}
}
}
}
else
{
if ( m_touchedPhysObject )
{
// check my position (physics object could have simulated into my position
// physics is not very far away, check my position
trace_t trace;
UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(),
MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace );
// is current position ok?
if ( trace.allsolid || trace.startsolid )
{
// stuck????!?!?
//Msg("Stuck on %s\n", trace.m_pEnt->GetClassname());
SetAbsOrigin( newPosition );
UTIL_TraceEntity( this, GetAbsOrigin(), GetAbsOrigin(),
MASK_PLAYERSOLID, this, COLLISION_GROUP_PLAYER_MOVEMENT, &trace );
if ( trace.allsolid || trace.startsolid )
{
//Msg("Double Stuck\n");
SetAbsOrigin( m_oldOrigin );
}
}
}
}
m_oldOrigin = GetAbsOrigin();
}
bool CPortal_Player::UseFoundEntity( CBaseEntity *pUseEntity )
{
bool usedSomething = false;
//!!!UNDONE: traceline here to prevent +USEing buttons through walls
int caps = pUseEntity->ObjectCaps();
variant_t emptyVariant;
if ( m_afButtonPressed & IN_USE )
{
// Robin: Don't play sounds for NPCs, because NPCs will allow respond with speech.
if ( !pUseEntity->MyNPCPointer() )
{
EmitSound( "HL2Player.Use" );
}
}
if ( ( (m_nButtons & IN_USE) && (caps & FCAP_CONTINUOUS_USE) ) ||
( (m_afButtonPressed & IN_USE) && (caps & (FCAP_IMPULSE_USE|FCAP_ONOFF_USE)) ) )
{
if ( caps & FCAP_CONTINUOUS_USE )
m_afPhysicsFlags |= PFLAG_USING;
pUseEntity->AcceptInput( "Use", this, this, emptyVariant, USE_TOGGLE );
usedSomething = true;
}
// UNDONE: Send different USE codes for ON/OFF. Cache last ONOFF_USE object to send 'off' if you turn away
else if ( (m_afButtonReleased & IN_USE) && (pUseEntity->ObjectCaps() & FCAP_ONOFF_USE) ) // BUGBUG This is an "off" use
{
pUseEntity->AcceptInput( "Use", this, this, emptyVariant, USE_TOGGLE );
usedSomething = true;
}
#if HL2_SINGLE_PRIMARY_WEAPON_MODE
//Check for weapon pick-up
if ( m_afButtonPressed & IN_USE )
{
CBaseCombatWeapon *pWeapon = dynamic_cast<CBaseCombatWeapon *>(pUseEntity);
if ( ( pWeapon != NULL ) && ( Weapon_CanSwitchTo( pWeapon ) ) )
{
//Try to take ammo or swap the weapon
if ( Weapon_OwnsThisType( pWeapon->GetClassname(), pWeapon->GetSubType() ) )
{
Weapon_EquipAmmoOnly( pWeapon );
}
else
{
Weapon_DropSlot( pWeapon->GetSlot() );
Weapon_Equip( pWeapon );
}
usedSomething = true;
}
}
#endif
return usedSomething;
}
//bool CPortal_Player::StartReplayMode( float fDelay, float fDuration, int iEntity )
//{
// if ( !BaseClass::StartReplayMode( fDelay, fDuration, 1 ) )
// return false;
//
// CSingleUserRecipientFilter filter( this );
// filter.MakeReliable();
//
// UserMessageBegin( filter, "KillCam" );
//
// EHANDLE hPlayer = this;
//
// if ( m_hObserverTarget.Get() )
// {
// WRITE_EHANDLE( m_hObserverTarget ); // first target
// WRITE_EHANDLE( hPlayer ); //second target
// }
// else
// {
// WRITE_EHANDLE( hPlayer ); // first target
// WRITE_EHANDLE( 0 ); //second target
// }
// MessageEnd();
//
// return true;
//}
//
//void CPortal_Player::StopReplayMode()
//{
// BaseClass::StopReplayMode();
//
// CSingleUserRecipientFilter filter( this );
// filter.MakeReliable();
//
// UserMessageBegin( filter, "KillCam" );
// WRITE_EHANDLE( 0 );
// WRITE_EHANDLE( 0 );
// MessageEnd();
//}
void CPortal_Player::PlayerUse( void )
{
// Was use pressed or released?
if ( ! ((m_nButtons | m_afButtonPressed | m_afButtonReleased) & IN_USE) )
return;
if ( m_afButtonPressed & IN_USE )
{
// Currently using a latched entity?
if ( ClearUseEntity() )
{
return;
}
else
{
if ( m_afPhysicsFlags & PFLAG_DIROVERRIDE )
{
m_afPhysicsFlags &= ~PFLAG_DIROVERRIDE;
m_iTrain = TRAIN_NEW|TRAIN_OFF;
return;
}
else
{ // Start controlling the train!
CBaseEntity *pTrain = GetGroundEntity();
if ( pTrain && !(m_nButtons & IN_JUMP) && (GetFlags() & FL_ONGROUND) && (pTrain->ObjectCaps() & FCAP_DIRECTIONAL_USE) && pTrain->OnControls(this) )
{
m_afPhysicsFlags |= PFLAG_DIROVERRIDE;
m_iTrain = TrainSpeed(pTrain->m_flSpeed, ((CFuncTrackTrain*)pTrain)->GetMaxSpeed());
m_iTrain |= TRAIN_NEW;
EmitSound( "HL2Player.TrainUse" );
return;
}
}
}
// Tracker 3926: We can't +USE something if we're climbing a ladder
if ( GetMoveType() == MOVETYPE_LADDER )
{
return;
}
}
CBaseEntity *pUseEntity = FindUseEntity();
bool usedSomething = false;
// Found an object
if ( pUseEntity )
{
SetHeldObjectOnOppositeSideOfPortal( false );
// TODO: Removed because we no longer have ghost animatings. May need to rework this code.
//// If we found a ghost animating then it needs to be held across a portal
//CGhostAnimating *pGhostAnimating = dynamic_cast<CGhostAnimating*>( pUseEntity );
//if ( pGhostAnimating )
//{
// CProp_Portal *pPortal = NULL;
// CPortalSimulator *pPortalSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pGhostAnimating->GetSourceEntity() );
// //HACKHACK: This assumes all portal simulators are a member of a prop_portal
// pPortal = (CProp_Portal *)(((char *)pPortalSimulator) - ((int)&(((CProp_Portal *)0)->m_PortalSimulator)));
// Assert( (&(pPortal->m_PortalSimulator)) == pPortalSimulator ); //doublechecking the hack
// if ( pPortal )
// {
// SetHeldObjectPortal( pPortal->m_hLinkedPortal );
// SetHeldObjectOnOppositeSideOfPortal( true );
// }
//}
usedSomething = UseFoundEntity( pUseEntity );
}
else
{
Vector forward;
EyeVectors( &forward, NULL, NULL );
Vector start = EyePosition();
Ray_t rayPortalTest;
rayPortalTest.Init( start, start + forward * PLAYER_USE_RADIUS );
float fMustBeCloserThan = 2.0f;
CProp_Portal *pPortal = UTIL_Portal_FirstAlongRay( rayPortalTest, fMustBeCloserThan );
if ( pPortal )
{
SetHeldObjectPortal( pPortal );
pUseEntity = FindUseEntityThroughPortal();
}
if ( pUseEntity )
{
SetHeldObjectOnOppositeSideOfPortal( true );
usedSomething = UseFoundEntity( pUseEntity );
}
else if ( m_afButtonPressed & IN_USE )
{
// Signal that we want to play the deny sound, unless the user is +USEing on a ladder!
// The sound is emitted in ItemPostFrame, since that occurs after GameMovement::ProcessMove which
// lets the ladder code unset this flag.
m_bPlayUseDenySound = true;
}
}
// Debounce the use key
if ( usedSomething && pUseEntity )
{
m_Local.m_nOldButtons |= IN_USE;
m_afButtonPressed &= ~IN_USE;
}
}
void CPortal_Player::PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper)
{
if( m_bFixEyeAnglesFromPortalling )
{
//the idea here is to handle the notion that the player has portalled, but they sent us an angle update before receiving that message.
//If we don't handle this here, we end up sending back their old angles which makes them hiccup their angles for a frame
float fOldAngleDiff = fabs( AngleDistance( ucmd->viewangles.x, m_qPrePortalledViewAngles.x ) );
fOldAngleDiff += fabs( AngleDistance( ucmd->viewangles.y, m_qPrePortalledViewAngles.y ) );
fOldAngleDiff += fabs( AngleDistance( ucmd->viewangles.z, m_qPrePortalledViewAngles.z ) );
float fCurrentAngleDiff = fabs( AngleDistance( ucmd->viewangles.x, pl.v_angle.x ) );
fCurrentAngleDiff += fabs( AngleDistance( ucmd->viewangles.y, pl.v_angle.y ) );
fCurrentAngleDiff += fabs( AngleDistance( ucmd->viewangles.z, pl.v_angle.z ) );
if( fCurrentAngleDiff > fOldAngleDiff )
ucmd->viewangles = TransformAnglesToWorldSpace( ucmd->viewangles, m_matLastPortalled.As3x4() );
m_bFixEyeAnglesFromPortalling = false;
}
BaseClass::PlayerRunCommand( ucmd, moveHelper );
}
bool CPortal_Player::ClientCommand( const CCommand &args )
{
if ( FStrEq( args[0], "spectate" ) )
{
// do nothing.
return true;
}
return BaseClass::ClientCommand( args );
}
void CPortal_Player::CheatImpulseCommands( int iImpulse )
{
switch ( iImpulse )
{
case 101:
{
if( sv_cheats->GetBool() )
{
GiveAllItems();
}
}
break;
default:
BaseClass::CheatImpulseCommands( iImpulse );
}
}
void CPortal_Player::CreateViewModel( int index /*=0*/ )
{
BaseClass::CreateViewModel( index );
return;
Assert( index >= 0 && index < MAX_VIEWMODELS );
if ( GetViewModel( index ) )
return;
CPredictedViewModel *vm = ( CPredictedViewModel * )CreateEntityByName( "predicted_viewmodel" );
if ( vm )
{
vm->SetAbsOrigin( GetAbsOrigin() );
vm->SetOwner( this );
vm->SetIndex( index );
DispatchSpawn( vm );
vm->FollowEntity( this, false );
m_hViewModel.Set( index, vm );
}
}
bool CPortal_Player::BecomeRagdollOnClient( const Vector &force )
{
return true;//BaseClass::BecomeRagdollOnClient( force );
}
void CPortal_Player::CreateRagdollEntity( const CTakeDamageInfo &info )
{
if ( m_hRagdoll )
{
UTIL_RemoveImmediate( m_hRagdoll );
m_hRagdoll = NULL;
}
#if PORTAL_HIDE_PLAYER_RAGDOLL
AddSolidFlags( FSOLID_NOT_SOLID );
AddEffects( EF_NODRAW | EF_NOSHADOW );
AddEFlags( EFL_NO_DISSOLVE );
#endif // PORTAL_HIDE_PLAYER_RAGDOLL
CBaseEntity *pRagdoll = CreateServerRagdoll( this, m_nForceBone, info, COLLISION_GROUP_INTERACTIVE_DEBRIS, true );
pRagdoll->m_takedamage = DAMAGE_NO;
m_hRagdoll = pRagdoll;
/*
// If we already have a ragdoll destroy it.
CPortalRagdoll *pRagdoll = dynamic_cast<CPortalRagdoll*>( m_hRagdoll.Get() );
if( pRagdoll )
{
UTIL_Remove( pRagdoll );
pRagdoll = NULL;
}
Assert( pRagdoll == NULL );
// Create a ragdoll.
pRagdoll = dynamic_cast<CPortalRagdoll*>( CreateEntityByName( "portal_ragdoll" ) );
if ( pRagdoll )
{
pRagdoll->m_hPlayer = this;
pRagdoll->m_vecRagdollOrigin = GetAbsOrigin();
pRagdoll->m_vecRagdollVelocity = GetAbsVelocity();
pRagdoll->m_nModelIndex = m_nModelIndex;
pRagdoll->m_nForceBone = m_nForceBone;
pRagdoll->CopyAnimationDataFrom( this );
pRagdoll->SetOwnerEntity( this );
pRagdoll->m_flAnimTime = gpGlobals->curtime;
pRagdoll->m_flPlaybackRate = 0.0;
pRagdoll->SetCycle( 0 );
pRagdoll->ResetSequence( 0 );
float fSequenceDuration = SequenceDuration( GetSequence() );
float fPreviousCycle = clamp(GetCycle()-( 0.1 * ( 1 / fSequenceDuration ) ),0.f,1.f);
float fCurCycle = GetCycle();
matrix3x4_t pBoneToWorld[MAXSTUDIOBONES], pBoneToWorldNext[MAXSTUDIOBONES];
SetupBones( pBoneToWorldNext, BONE_USED_BY_ANYTHING );
SetCycle( fPreviousCycle );
SetupBones( pBoneToWorld, BONE_USED_BY_ANYTHING );
SetCycle( fCurCycle );
pRagdoll->InitRagdoll( info.GetDamageForce(), m_nForceBone, info.GetDamagePosition(), pBoneToWorld, pBoneToWorldNext, 0.1f, COLLISION_GROUP_INTERACTIVE_DEBRIS, true );
pRagdoll->SetMoveType( MOVETYPE_VPHYSICS );
pRagdoll->SetSolid( SOLID_VPHYSICS );
if ( IsDissolving() )
{
pRagdoll->TransferDissolveFrom( this );
}
Vector mins, maxs;
mins = CollisionProp()->OBBMins();
maxs = CollisionProp()->OBBMaxs();
pRagdoll->CollisionProp()->SetCollisionBounds( mins, maxs );
pRagdoll->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
}
// Turn off the player.
AddSolidFlags( FSOLID_NOT_SOLID );
AddEffects( EF_NODRAW | EF_NOSHADOW );
SetMoveType( MOVETYPE_NONE );
// Save ragdoll handle.
m_hRagdoll = pRagdoll;
*/
}
void CPortal_Player::Jump( void )
{
g_PortalGameStats.Event_PlayerJump( GetAbsOrigin(), GetAbsVelocity() );
BaseClass::Jump();
}
void CPortal_Player::Event_Killed( const CTakeDamageInfo &info )
{
//update damage info with our accumulated physics force
CTakeDamageInfo subinfo = info;
subinfo.SetDamageForce( m_vecTotalBulletForce );
// show killer in death cam mode
// chopped down version of SetObserverTarget without the team check
//if( info.GetAttacker() )
//{
// // set new target
// m_hObserverTarget.Set( info.GetAttacker() );
//}
//else
// m_hObserverTarget.Set( NULL );
UpdateExpression();
// Note: since we're dead, it won't draw us on the client, but we don't set EF_NODRAW
// because we still want to transmit to the clients in our PVS.
CreateRagdollEntity( info );
BaseClass::Event_Killed( subinfo );
#if PORTAL_HIDE_PLAYER_RAGDOLL
// Fizzle all portals so they don't see the player disappear
int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
for( int i = 0; i != iPortalCount; ++i )
{
CProp_Portal *pTempPortal = pPortals[i];
if( pTempPortal && pTempPortal->m_bActivated )
{
pTempPortal->Fizzle();
}
}
#endif // PORTAL_HIDE_PLAYER_RAGDOLL
if ( (info.GetDamageType() & DMG_DISSOLVE) && !(m_hRagdoll.Get()->GetEFlags() & EFL_NO_DISSOLVE) )
{
if ( m_hRagdoll )
{
m_hRagdoll->GetBaseAnimating()->Dissolve( NULL, gpGlobals->curtime, false, ENTITY_DISSOLVE_NORMAL );
}
}
m_lifeState = LIFE_DYING;
StopZooming();
if ( GetObserverTarget() )
{
//StartReplayMode( 3, 3, GetObserverTarget()->entindex() );
//StartObserverMode( OBS_MODE_DEATHCAM );
}
}
int CPortal_Player::OnTakeDamage( const CTakeDamageInfo &inputInfo )
{
CTakeDamageInfo inputInfoCopy( inputInfo );
// If you shoot yourself, make it hurt but push you less
if ( inputInfoCopy.GetAttacker() == this && inputInfoCopy.GetDamageType() == DMG_BULLET )
{
inputInfoCopy.ScaleDamage( 5.0f );
inputInfoCopy.ScaleDamageForce( 0.05f );
}
CBaseEntity *pAttacker = inputInfoCopy.GetAttacker();
CBaseEntity *pInflictor = inputInfoCopy.GetInflictor();
bool bIsTurret = false;
if ( pAttacker && FClassnameIs( pAttacker, "npc_portal_turret_floor" ) )
bIsTurret = true;
// Refuse damage from prop_glados_core.
if ( (pAttacker && FClassnameIs( pAttacker, "prop_glados_core" )) ||
(pInflictor && FClassnameIs( pInflictor, "prop_glados_core" )) )
{
inputInfoCopy.SetDamage(0.0f);
}
if ( bIsTurret && ( inputInfoCopy.GetDamageType() & DMG_BULLET ) )
{
Vector vLateralForce = inputInfoCopy.GetDamageForce();
vLateralForce.z = 0.0f;
// Push if the player is moving against the force direction
if ( GetAbsVelocity().Dot( vLateralForce ) < 0.0f )
ApplyAbsVelocityImpulse( vLateralForce );
}
else if ( ( inputInfoCopy.GetDamageType() & DMG_CRUSH ) )
{
if ( bIsTurret )
{
inputInfoCopy.SetDamage( inputInfoCopy.GetDamage() * 0.5f );
}
if ( inputInfoCopy.GetDamage() >= 10.0f )
{
EmitSound( "PortalPlayer.BonkYelp" );
}
}
else if ( ( inputInfoCopy.GetDamageType() & DMG_SHOCK ) || ( inputInfoCopy.GetDamageType() & DMG_BURN ) )
{
EmitSound( "PortalPortal.PainYelp" );
}
int ret = BaseClass::OnTakeDamage( inputInfoCopy );
// Copy the multidamage damage origin over what the base class wrote, because
// that gets translated correctly though portals.
m_DmgOrigin = inputInfo.GetDamagePosition();
if ( GetHealth() < 100 )
{
m_fTimeLastHurt = gpGlobals->curtime;
}
return ret;
}
int CPortal_Player::OnTakeDamage_Alive( const CTakeDamageInfo &info )
{
// set damage type sustained
m_bitsDamageType |= info.GetDamageType();
if ( !CBaseCombatCharacter::OnTakeDamage_Alive( info ) )
return 0;
CBaseEntity * attacker = info.GetAttacker();
if ( !attacker )
return 0;
Vector vecDir = vec3_origin;
if ( info.GetInflictor() )
{
vecDir = info.GetInflictor()->WorldSpaceCenter() - Vector ( 0, 0, 10 ) - WorldSpaceCenter();
VectorNormalize( vecDir );
}
if ( info.GetInflictor() && (GetMoveType() == MOVETYPE_WALK) &&
( !attacker->IsSolidFlagSet(FSOLID_TRIGGER)) )
{
Vector force = vecDir;// * -DamageForce( WorldAlignSize(), info.GetBaseDamage() );
if ( force.z > 250.0f )
{
force.z = 250.0f;
}
ApplyAbsVelocityImpulse( force );
}
// fire global game event
IGameEvent * event = gameeventmanager->CreateEvent( "player_hurt" );
if ( event )
{
event->SetInt("userid", GetUserID() );
event->SetInt("health", MAX(0, m_iHealth) );
event->SetInt("priority", 5 ); // HLTV event priority, not transmitted
if ( attacker->IsPlayer() )
{
CBasePlayer *player = ToBasePlayer( attacker );
event->SetInt("attacker", player->GetUserID() ); // hurt by other player
}
else
{
event->SetInt("attacker", 0 ); // hurt by "world"
}
gameeventmanager->FireEvent( event );
}
// Insert a combat sound so that nearby NPCs hear battle
if ( attacker->IsNPC() )
{
CSoundEnt::InsertSound( SOUND_COMBAT, GetAbsOrigin(), 512, 0.5, this );//<<TODO>>//magic number
}
return 1;
}
void CPortal_Player::ForceDuckThisFrame( void )
{
if( m_Local.m_bDucked != true )
{
//m_Local.m_bDucking = false;
m_Local.m_bDucked = true;
ForceButtons( IN_DUCK );
AddFlag( FL_DUCKING );
SetVCollisionState( GetAbsOrigin(), GetAbsVelocity(), VPHYS_CROUCH );
}
}
void CPortal_Player::UnDuck( void )
{
if( m_Local.m_bDucked != false )
{
m_Local.m_bDucked = false;
UnforceButtons( IN_DUCK );
RemoveFlag( FL_DUCKING );
SetVCollisionState( GetAbsOrigin(), GetAbsVelocity(), VPHYS_WALK );
}
}
//-----------------------------------------------------------------------------
// Purpose: Overload for portal-- Our player can lift his own mass.
// Input : *pObject - The object to lift
// bLimitMassAndSize - check for mass/size limits
//-----------------------------------------------------------------------------
void CPortal_Player::PickupObject(CBaseEntity *pObject, bool bLimitMassAndSize )
{
// can't pick up what you're standing on
if ( GetGroundEntity() == pObject )
return;
if ( bLimitMassAndSize == true )
{
if ( CBasePlayer::CanPickupObject( pObject, PORTAL_PLAYER_MAX_LIFT_MASS, PORTAL_PLAYER_MAX_LIFT_SIZE ) == false )
return;
}
// Can't be picked up if NPCs are on me
if ( pObject->HasNPCsOnIt() )
return;
PlayerPickupObject( this, pObject );
}
void CPortal_Player::ForceDropOfCarriedPhysObjects( CBaseEntity *pOnlyIfHoldingThis )
{
m_bHeldObjectOnOppositeSideOfPortal = false;
BaseClass::ForceDropOfCarriedPhysObjects( pOnlyIfHoldingThis );
}
void CPortal_Player::IncrementPortalsPlaced( void )
{
m_StatsThisLevel.iNumPortalsPlaced++;
if ( m_iBonusChallenge == PORTAL_CHALLENGE_PORTALS )
SetBonusProgress( static_cast<int>( m_StatsThisLevel.iNumPortalsPlaced ) );
}
void CPortal_Player::IncrementStepsTaken( void )
{
m_StatsThisLevel.iNumStepsTaken++;
if ( m_iBonusChallenge == PORTAL_CHALLENGE_STEPS )
SetBonusProgress( static_cast<int>( m_StatsThisLevel.iNumStepsTaken ) );
}
void CPortal_Player::UpdateSecondsTaken( void )
{
float fSecondsSinceLastUpdate = ( gpGlobals->curtime - m_fTimeLastNumSecondsUpdate );
m_StatsThisLevel.fNumSecondsTaken += fSecondsSinceLastUpdate;
m_fTimeLastNumSecondsUpdate = gpGlobals->curtime;
if ( m_iBonusChallenge == PORTAL_CHALLENGE_TIME )
SetBonusProgress( static_cast<int>( m_StatsThisLevel.fNumSecondsTaken ) );
if ( m_fNeuroToxinDamageTime > 0.0f )
{
float fTimeRemaining = m_fNeuroToxinDamageTime - gpGlobals->curtime;
if ( fTimeRemaining < 0.0f )
{
CTakeDamageInfo info;
info.SetDamage( gpGlobals->frametime * 50.0f );
info.SetDamageType( DMG_NERVEGAS );
TakeDamage( info );
fTimeRemaining = 0.0f;
}
PauseBonusProgress( false );
SetBonusProgress( static_cast<int>( fTimeRemaining ) );
}
}
void CPortal_Player::ResetThisLevelStats( void )
{
m_StatsThisLevel.iNumPortalsPlaced = 0;
m_StatsThisLevel.iNumStepsTaken = 0;
m_StatsThisLevel.fNumSecondsTaken = 0.0f;
if ( m_iBonusChallenge != PORTAL_CHALLENGE_NONE )
SetBonusProgress( 0 );
}
//-----------------------------------------------------------------------------
// Purpose: Update the area bits variable which is networked down to the client to determine
// which area portals should be closed based on visibility.
// Input : *pvs - pvs to be used to determine visibility of the portals
//-----------------------------------------------------------------------------
void CPortal_Player::UpdatePortalViewAreaBits( unsigned char *pvs, int pvssize )
{
Assert ( pvs );
int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
if( iPortalCount == 0 )
return;
CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
int *portalArea = (int *)stackalloc( sizeof( int ) * iPortalCount );
bool *bUsePortalForVis = (bool *)stackalloc( sizeof( bool ) * iPortalCount );
unsigned char *portalTempBits = (unsigned char *)stackalloc( sizeof( unsigned char ) * 32 * iPortalCount );
COMPILE_TIME_ASSERT( (sizeof( unsigned char ) * 32) >= sizeof( ((CPlayerLocalData*)0)->m_chAreaBits ) );
// setup area bits for these portals
for ( int i = 0; i < iPortalCount; ++i )
{
CProp_Portal* pLocalPortal = pPortals[ i ];
// Make sure this portal is active before adding it's location to the pvs
if ( pLocalPortal && pLocalPortal->m_bActivated )
{
CProp_Portal* pRemotePortal = pLocalPortal->m_hLinkedPortal.Get();
// Make sure this portal's linked portal is in the PVS before we add what it can see
if ( pRemotePortal && pRemotePortal->m_bActivated && pRemotePortal->NetworkProp() &&
pRemotePortal->NetworkProp()->IsInPVS( edict(), pvs, pvssize ) )
{
portalArea[ i ] = engine->GetArea( pPortals[ i ]->GetAbsOrigin() );
if ( portalArea [ i ] >= 0 )
{
bUsePortalForVis[ i ] = true;
}
engine->GetAreaBits( portalArea[ i ], &portalTempBits[ i * 32 ], sizeof( unsigned char ) * 32 );
}
}
}
// Use the union of player-view area bits and the portal-view area bits of each portal
for ( int i = 0; i < m_Local.m_chAreaBits.Count(); i++ )
{
for ( int j = 0; j < iPortalCount; ++j )
{
// If this portal is active, in PVS and it's location is valid
if ( bUsePortalForVis[ j ] )
{
m_Local.m_chAreaBits.Set( i, m_Local.m_chAreaBits[ i ] | portalTempBits[ (j * 32) + i ] );
}
}
}
}
//////////////////////////////////////////////////////////////////////////
// AddPortalCornersToEnginePVS
// Subroutine to wrap the adding of portal corners to the PVS which is called once for the setup of each portal.
// input - pPortal: the portal we are viewing 'out of' which needs it's corners added to the PVS
//////////////////////////////////////////////////////////////////////////
void AddPortalCornersToEnginePVS( CProp_Portal* pPortal )
{
Assert ( pPortal );
if ( !pPortal )
return;
Vector vForward, vRight, vUp;
pPortal->GetVectors( &vForward, &vRight, &vUp );
// Center of the remote portal
Vector ptOrigin = pPortal->GetAbsOrigin();
// Distance offsets to the different edges of the portal... Used in the placement checks
Vector vToTopEdge = vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS );
Vector vToBottomEdge = -vToTopEdge;
Vector vToRightEdge = vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS );
Vector vToLeftEdge = -vToRightEdge;
// Distance to place PVS points away from portal, to avoid being in solid
Vector vForwardBump = vForward * 1.0f;
// Add center and edges to the engine PVS
engine->AddOriginToPVS( ptOrigin + vForwardBump);
engine->AddOriginToPVS( ptOrigin + vToTopEdge + vToLeftEdge + vForwardBump );
engine->AddOriginToPVS( ptOrigin + vToTopEdge + vToRightEdge + vForwardBump );
engine->AddOriginToPVS( ptOrigin + vToBottomEdge + vToLeftEdge + vForwardBump );
engine->AddOriginToPVS( ptOrigin + vToBottomEdge + vToRightEdge + vForwardBump );
}
void PortalSetupVisibility( CBaseEntity *pPlayer, int area, unsigned char *pvs, int pvssize )
{
int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
if( iPortalCount == 0 )
return;
CProp_Portal **pPortals = CProp_Portal_Shared::AllPortals.Base();
for( int i = 0; i != iPortalCount; ++i )
{
CProp_Portal *pPortal = pPortals[i];
if ( pPortal && pPortal->m_bActivated )
{
if ( pPortal->NetworkProp()->IsInPVS( pPlayer->edict(), pvs, pvssize ) )
{
if ( engine->CheckAreasConnected( area, pPortal->NetworkProp()->AreaNum() ) )
{
CProp_Portal *pLinkedPortal = static_cast<CProp_Portal*>( pPortal->m_hLinkedPortal.Get() );
if ( pLinkedPortal )
{
AddPortalCornersToEnginePVS ( pLinkedPortal );
}
}
}
}
}
}
void CPortal_Player::SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize )
{
BaseClass::SetupVisibility( pViewEntity, pvs, pvssize );
int area = pViewEntity ? pViewEntity->NetworkProp()->AreaNum() : NetworkProp()->AreaNum();
// At this point the EyePosition has been added as a view origin, but if we are currently stuck
// in a portal, our EyePosition may return a point in solid. Find the reflected eye position
// and use that as a vis origin instead.
if ( m_hPortalEnvironment )
{
CProp_Portal *pPortal = NULL, *pRemotePortal = NULL;
pPortal = m_hPortalEnvironment;
pRemotePortal = pPortal->m_hLinkedPortal;
if ( pPortal && pRemotePortal && pPortal->m_bActivated && pRemotePortal->m_bActivated )
{
Vector ptPortalCenter = pPortal->GetAbsOrigin();
Vector vPortalForward;
pPortal->GetVectors( &vPortalForward, NULL, NULL );
Vector eyeOrigin = EyePosition();
Vector vEyeToPortalCenter = ptPortalCenter - eyeOrigin;
float fPortalDist = vPortalForward.Dot( vEyeToPortalCenter );
if( fPortalDist > 0.0f ) //eye point is behind portal
{
// Move eye origin to it's transformed position on the other side of the portal
UTIL_Portal_PointTransform( pPortal->MatrixThisToLinked(), eyeOrigin, eyeOrigin );
// Use this as our view origin (as this is where the client will be displaying from)
engine->AddOriginToPVS( eyeOrigin );
if ( !pViewEntity || pViewEntity->IsPlayer() )
{
area = engine->GetArea( eyeOrigin );
}
}
}
}
PortalSetupVisibility( this, area, pvs, pvssize );
}
#ifdef PORTAL_MP
CBaseEntity* CPortal_Player::EntSelectSpawnPoint( void )
{
CBaseEntity *pSpot = NULL;
CBaseEntity *pLastSpawnPoint = g_pLastSpawn;
edict_t *player = edict();
const char *pSpawnpointName = "info_player_start";
/*if ( HL2MPRules()->IsTeamplay() == true )
{
if ( GetTeamNumber() == TEAM_COMBINE )
{
pSpawnpointName = "info_player_combine";
pLastSpawnPoint = g_pLastCombineSpawn;
}
else if ( GetTeamNumber() == TEAM_REBELS )
{
pSpawnpointName = "info_player_rebel";
pLastSpawnPoint = g_pLastRebelSpawn;
}
if ( gEntList.FindEntityByClassname( NULL, pSpawnpointName ) == NULL )
{
pSpawnpointName = "info_player_deathmatch";
pLastSpawnPoint = g_pLastSpawn;
}
}*/
pSpot = pLastSpawnPoint;
// Randomize the start spot
for ( int i = random->RandomInt(1,5); i > 0; i-- )
pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName );
if ( !pSpot ) // skip over the null point
pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName );
CBaseEntity *pFirstSpot = pSpot;
do
{
if ( pSpot )
{
// check if pSpot is valid
if ( g_pGameRules->IsSpawnPointValid( pSpot, this ) )
{
if ( pSpot->GetLocalOrigin() == vec3_origin )
{
pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName );
continue;
}
// if so, go to pSpot
goto ReturnSpot;
}
}
// increment pSpot
pSpot = gEntList.FindEntityByClassname( pSpot, pSpawnpointName );
} while ( pSpot != pFirstSpot ); // loop if we're not back to the start
// we haven't found a place to spawn yet, so kill any guy at the first spawn point and spawn there
if ( pSpot )
{
CBaseEntity *ent = NULL;
for ( CEntitySphereQuery sphere( pSpot->GetAbsOrigin(), 128 ); (ent = sphere.GetCurrentEntity()) != NULL; sphere.NextEntity() )
{
// if ent is a client, kill em (unless they are ourselves)
if ( ent->IsPlayer() && !(ent->edict() == player) )
ent->TakeDamage( CTakeDamageInfo( GetContainingEntity(INDEXENT(0)), GetContainingEntity(INDEXENT(0)), 300, DMG_GENERIC ) );
}
goto ReturnSpot;
}
if ( !pSpot )
{
pSpot = gEntList.FindEntityByClassname( pSpot, "info_player_start" );
if ( pSpot )
goto ReturnSpot;
}
ReturnSpot:
/*if ( HL2MPRules()->IsTeamplay() == true )
{
if ( GetTeamNumber() == TEAM_COMBINE )
{
g_pLastCombineSpawn = pSpot;
}
else if ( GetTeamNumber() == TEAM_REBELS )
{
g_pLastRebelSpawn = pSpot;
}
}*/
g_pLastSpawn = pSpot;
//m_flSlamProtectTime = gpGlobals->curtime + 0.5;
return pSpot;
}
void CPortal_Player::PickTeam( void )
{
//picks lowest or random
CTeam *pCombine = g_Teams[TEAM_COMBINE];
CTeam *pRebels = g_Teams[TEAM_REBELS];
if ( pCombine->GetNumPlayers() > pRebels->GetNumPlayers() )
{
ChangeTeam( TEAM_REBELS );
}
else if ( pCombine->GetNumPlayers() < pRebels->GetNumPlayers() )
{
ChangeTeam( TEAM_COMBINE );
}
else
{
ChangeTeam( random->RandomInt( TEAM_COMBINE, TEAM_REBELS ) );
}
}
#endif
CON_COMMAND( startadmiregloves, "Starts the admire gloves animation." )
{
CPortal_Player *pPlayer = (CPortal_Player *)UTIL_GetCommandClient();
if( pPlayer == NULL )
pPlayer = GetPortalPlayer( 1 ); //last ditch effort
if( pPlayer )
pPlayer->StartAdmireGlovesAnimation();
}
CON_COMMAND( displayportalplayerstats, "Displays current level stats for portals placed, steps taken, and seconds taken." )
{
CPortal_Player *pPlayer = (CPortal_Player *)UTIL_GetCommandClient();
if( pPlayer == NULL )
pPlayer = GetPortalPlayer( 1 ); //last ditch effort
if( pPlayer )
{
int iMinutes = static_cast<int>( pPlayer->NumSecondsTaken() / 60.0f );
int iSeconds = static_cast<int>( pPlayer->NumSecondsTaken() ) % 60;
CFmtStr msg;
NDebugOverlay::ScreenText( 0.5f, 0.5f, msg.sprintf( "Portals Placed: %d\nSteps Taken: %d\nTime: %d:%d", pPlayer->NumPortalsPlaced(), pPlayer->NumStepsTaken(), iMinutes, iSeconds ), 255, 255, 255, 150, 5.0f );
}
}
CON_COMMAND( startneurotoxins, "Starts the nerve gas timer." )
{
CPortal_Player *pPlayer = (CPortal_Player *)UTIL_GetCommandClient();
if( pPlayer == NULL )
pPlayer = GetPortalPlayer( 1 ); //last ditch effort
float fCoundownTime = 180.0f;
if ( args.ArgC() > 1 )
fCoundownTime = atof( args[ 1 ] );
if( pPlayer )
pPlayer->SetNeuroToxinDamageTime( fCoundownTime );
}