source-engine/game/server/portal/prop_portal.cpp
2022-04-16 12:05:19 +03:00

2316 lines
73 KiB
C++

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#include "cbase.h"
#include "prop_portal.h"
#include "portal_player.h"
#include "portal/weapon_physcannon.h"
#include "physics_npc_solver.h"
#include "envmicrophone.h"
#include "env_speaker.h"
#include "func_portal_detector.h"
#include "model_types.h"
#include "te_effect_dispatch.h"
#include "collisionutils.h"
#include "physobj.h"
#include "world.h"
#include "hierarchy.h"
#include "physics_saverestore.h"
#include "PhysicsCloneArea.h"
#include "portal_gamestats.h"
#include "prop_portal_shared.h"
#include "weapon_portalgun.h"
#include "portal_placement.h"
#include "physicsshadowclone.h"
#include "particle_parse.h"
#include "rumble_shared.h"
#include "func_portal_orientation.h"
#include "env_debughistory.h"
#include "tier1/callqueue.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY 50.0f
#define MINIMUM_FLOOR_TO_FLOOR_PORTAL_EXIT_VELOCITY 225.0f
#define MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY_PLAYER 300.0f
#define MAXIMUM_PORTAL_EXIT_VELOCITY 1000.0f
CCallQueue *GetPortalCallQueue();
ConVar sv_portal_debug_touch("sv_portal_debug_touch", "0", FCVAR_REPLICATED );
ConVar sv_portal_placement_never_fail("sv_portal_placement_never_fail", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
ConVar sv_portal_new_velocity_check("sv_portal_new_velocity_check", "1", FCVAR_CHEAT );
static CUtlVector<CProp_Portal *> s_PortalLinkageGroups[256];
BEGIN_DATADESC( CProp_Portal )
//saving
DEFINE_FIELD( m_hLinkedPortal, FIELD_EHANDLE ),
DEFINE_KEYFIELD( m_iLinkageGroupID, FIELD_CHARACTER, "LinkageGroupID" ),
DEFINE_FIELD( m_matrixThisToLinked, FIELD_VMATRIX ),
DEFINE_KEYFIELD( m_bActivated, FIELD_BOOLEAN, "Activated" ),
DEFINE_KEYFIELD( m_bIsPortal2, FIELD_BOOLEAN, "PortalTwo" ),
DEFINE_FIELD( m_vPrevForward, FIELD_VECTOR ),
DEFINE_FIELD( m_hMicrophone, FIELD_EHANDLE ),
DEFINE_FIELD( m_hSpeaker, FIELD_EHANDLE ),
DEFINE_SOUNDPATCH( m_pAmbientSound ),
DEFINE_FIELD( m_vAudioOrigin, FIELD_VECTOR ),
DEFINE_FIELD( m_vDelayedPosition, FIELD_VECTOR ),
DEFINE_FIELD( m_qDelayedAngles, FIELD_VECTOR ),
DEFINE_FIELD( m_iDelayedFailure, FIELD_INTEGER ),
DEFINE_FIELD( m_hPlacedBy, FIELD_EHANDLE ),
// DEFINE_FIELD( m_plane_Origin, cplane_t ),
// DEFINE_FIELD( m_pAttachedCloningArea, CPhysicsCloneArea ),
// DEFINE_FIELD( m_PortalSimulator, CPortalSimulator ),
// DEFINE_FIELD( m_pCollisionShape, CPhysCollide ),
DEFINE_FIELD( m_bSharedEnvironmentConfiguration, FIELD_BOOLEAN ),
DEFINE_ARRAY( m_vPortalCorners, FIELD_POSITION_VECTOR, 4 ),
// Function Pointers
DEFINE_THINKFUNC( DelayedPlacementThink ),
DEFINE_THINKFUNC( TestRestingSurfaceThink ),
DEFINE_THINKFUNC( FizzleThink ),
DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetActivatedState", InputSetActivatedState ),
DEFINE_INPUTFUNC( FIELD_VOID, "Fizzle", InputFizzle ),
DEFINE_INPUTFUNC( FIELD_STRING, "NewLocation", InputNewLocation ),
DEFINE_OUTPUT( m_OnPlacedSuccessfully, "OnPlacedSuccessfully" ),
END_DATADESC()
IMPLEMENT_SERVERCLASS_ST( CProp_Portal, DT_Prop_Portal )
SendPropEHandle( SENDINFO(m_hLinkedPortal) ),
SendPropBool( SENDINFO(m_bActivated) ),
SendPropBool( SENDINFO(m_bIsPortal2) ),
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( prop_portal, CProp_Portal );
CProp_Portal::CProp_Portal( void )
{
m_vPrevForward = Vector( 0.0f, 0.0f, 0.0f );
m_PortalSimulator.SetPortalSimulatorCallbacks( this );
// Init to something safe
for ( int i = 0; i < 4; ++i )
{
m_vPortalCorners[i] = Vector(0,0,0);
}
//create the collision shape.... TODO: consider having one shared collideable between all portals
float fPlanes[6*4];
fPlanes[(0*4) + 0] = 1.0f;
fPlanes[(0*4) + 1] = 0.0f;
fPlanes[(0*4) + 2] = 0.0f;
fPlanes[(0*4) + 3] = CProp_Portal_Shared::vLocalMaxs.x;
fPlanes[(1*4) + 0] = -1.0f;
fPlanes[(1*4) + 1] = 0.0f;
fPlanes[(1*4) + 2] = 0.0f;
fPlanes[(1*4) + 3] = -CProp_Portal_Shared::vLocalMins.x;
fPlanes[(2*4) + 0] = 0.0f;
fPlanes[(2*4) + 1] = 1.0f;
fPlanes[(2*4) + 2] = 0.0f;
fPlanes[(2*4) + 3] = CProp_Portal_Shared::vLocalMaxs.y;
fPlanes[(3*4) + 0] = 0.0f;
fPlanes[(3*4) + 1] = -1.0f;
fPlanes[(3*4) + 2] = 0.0f;
fPlanes[(3*4) + 3] = -CProp_Portal_Shared::vLocalMins.y;
fPlanes[(4*4) + 0] = 0.0f;
fPlanes[(4*4) + 1] = 0.0f;
fPlanes[(4*4) + 2] = 1.0f;
fPlanes[(4*4) + 3] = CProp_Portal_Shared::vLocalMaxs.z;
fPlanes[(5*4) + 0] = 0.0f;
fPlanes[(5*4) + 1] = 0.0f;
fPlanes[(5*4) + 2] = -1.0f;
fPlanes[(5*4) + 3] = -CProp_Portal_Shared::vLocalMins.z;
CPolyhedron *pPolyhedron = GeneratePolyhedronFromPlanes( fPlanes, 6, 0.00001f, true );
Assert( pPolyhedron != NULL );
CPhysConvex *pConvex = physcollision->ConvexFromConvexPolyhedron( *pPolyhedron );
pPolyhedron->Release();
Assert( pConvex != NULL );
m_pCollisionShape = physcollision->ConvertConvexToCollide( &pConvex, 1 );
CProp_Portal_Shared::AllPortals.AddToTail( this );
}
CProp_Portal::~CProp_Portal( void )
{
CProp_Portal_Shared::AllPortals.FindAndRemove( this );
s_PortalLinkageGroups[m_iLinkageGroupID].FindAndRemove( this );
}
void CProp_Portal::UpdateOnRemove( void )
{
m_PortalSimulator.ClearEverything();
RemovePortalMicAndSpeaker();
CProp_Portal *pRemote = m_hLinkedPortal;
if( pRemote != NULL )
{
m_PortalSimulator.DetachFromLinked();
m_hLinkedPortal = NULL;
m_bActivated = false;
pRemote->UpdatePortalLinkage();
pRemote->UpdatePortalTeleportMatrix();
}
if( m_pAttachedCloningArea )
{
UTIL_Remove( m_pAttachedCloningArea );
m_pAttachedCloningArea = NULL;
}
BaseClass::UpdateOnRemove();
}
void CProp_Portal::Precache( void )
{
PrecacheScriptSound( "Portal.ambient_loop" );
PrecacheScriptSound( "Portal.open_blue" );
PrecacheScriptSound( "Portal.open_red" );
PrecacheScriptSound( "Portal.close_blue" );
PrecacheScriptSound( "Portal.close_red" );
PrecacheScriptSound( "Portal.fizzle_moved" );
PrecacheScriptSound( "Portal.fizzle_invalid_surface" );
PrecacheModel( "models/portals/portal1.mdl" );
PrecacheModel( "models/portals/portal2.mdl" );
PrecacheParticleSystem( "portal_1_particles" );
PrecacheParticleSystem( "portal_2_particles" );
PrecacheParticleSystem( "portal_1_edge" );
PrecacheParticleSystem( "portal_2_edge" );
PrecacheParticleSystem( "portal_1_nofit" );
PrecacheParticleSystem( "portal_2_nofit" );
PrecacheParticleSystem( "portal_1_overlap" );
PrecacheParticleSystem( "portal_2_overlap" );
PrecacheParticleSystem( "portal_1_badvolume" );
PrecacheParticleSystem( "portal_2_badvolume" );
PrecacheParticleSystem( "portal_1_badsurface" );
PrecacheParticleSystem( "portal_2_badsurface" );
PrecacheParticleSystem( "portal_1_close" );
PrecacheParticleSystem( "portal_2_close" );
PrecacheParticleSystem( "portal_1_cleanser" );
PrecacheParticleSystem( "portal_2_cleanser" );
PrecacheParticleSystem( "portal_1_near" );
PrecacheParticleSystem( "portal_2_near" );
PrecacheParticleSystem( "portal_1_success" );
PrecacheParticleSystem( "portal_2_success" );
BaseClass::Precache();
}
void CProp_Portal::CreateSounds()
{
if (!m_pAmbientSound)
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
CPASAttenuationFilter filter( this );
m_pAmbientSound = controller.SoundCreate( filter, entindex(), "Portal.ambient_loop" );
controller.Play( m_pAmbientSound, 0, 100 );
}
}
void CProp_Portal::StopLoopingSounds()
{
if ( m_pAmbientSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pAmbientSound );
m_pAmbientSound = NULL;
}
BaseClass::StopLoopingSounds();
}
void CProp_Portal::Spawn( void )
{
Precache();
Assert( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) == -1 );
s_PortalLinkageGroups[m_iLinkageGroupID].AddToTail( this );
m_matrixThisToLinked.Identity(); //don't accidentally teleport objects to zero space
AddEffects( EF_NORECEIVESHADOW | EF_NOSHADOW );
SetSolid( SOLID_OBB );
SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID | FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
SetMoveType( MOVETYPE_NONE );
SetCollisionGroup( COLLISION_GROUP_PLAYER );
//VPhysicsInitNormal( SOLID_VPHYSICS, FSOLID_TRIGGER, false );
//CreateVPhysics();
ResetModel();
SetSize( CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs );
UpdateCorners();
BaseClass::Spawn();
m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
}
void CProp_Portal::OnRestore()
{
UpdateCorners();
Assert( m_pAttachedCloningArea == NULL );
m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
BaseClass::OnRestore();
if ( m_bActivated )
{
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_particles" ) : ( "portal_1_particles" ) ), PATTACH_POINT_FOLLOW, this, "particles_2", true );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_edge" ) : ( "portal_1_edge" ) ), PATTACH_POINT_FOLLOW, this, "particlespin" );
}
}
void DumpActiveCollision( const CPortalSimulator *pPortalSimulator, const char *szFileName );
void PortalSimulatorDumps_DumpCollideToGlView( CPhysCollide *pCollide, const Vector &origin, const QAngle &angles, float fColorScale, const char *pFilename );
bool CProp_Portal::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
{
physcollision->TraceBox( ray, MASK_ALL, NULL, m_pCollisionShape, GetAbsOrigin(), GetAbsAngles(), &tr );
return tr.DidHit();
}
//-----------------------------------------------------------------------------
// Purpose: Runs when a fired portal shot reaches it's destination wall. Detects current placement valididty state.
//-----------------------------------------------------------------------------
void CProp_Portal::DelayedPlacementThink( void )
{
Vector vOldOrigin = GetLocalOrigin();
QAngle qOldAngles = GetLocalAngles();
Vector vForward;
AngleVectors( m_qDelayedAngles, &vForward );
// Check if something made the spot invalid mid flight
// Bad surface and near fizzle effects take priority
if ( m_iDelayedFailure != PORTAL_FIZZLE_BAD_SURFACE && m_iDelayedFailure != PORTAL_FIZZLE_NEAR_BLUE && m_iDelayedFailure != PORTAL_FIZZLE_NEAR_RED )
{
if ( IsPortalOverlappingOtherPortals( this, m_vDelayedPosition, m_qDelayedAngles ) )
{
m_iDelayedFailure = PORTAL_FIZZLE_OVERLAPPED_LINKED;
}
else if ( IsPortalIntersectingNoPortalVolume( m_vDelayedPosition, m_qDelayedAngles, vForward ) )
{
m_iDelayedFailure = PORTAL_FIZZLE_BAD_VOLUME;
}
}
if ( sv_portal_placement_never_fail.GetBool() )
{
m_iDelayedFailure = PORTAL_FIZZLE_SUCCESS;
}
DoFizzleEffect( m_iDelayedFailure );
if ( m_iDelayedFailure != PORTAL_FIZZLE_SUCCESS )
{
// It didn't successfully place
return;
}
// Do effects at old location if it was active
if ( m_bActivated )
{
DoFizzleEffect( PORTAL_FIZZLE_CLOSE, false );
}
CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
if( pPortalGun )
{
CPortal_Player *pFiringPlayer = dynamic_cast<CPortal_Player *>( pPortalGun->GetOwner() );
if( pFiringPlayer )
{
pFiringPlayer->IncrementPortalsPlaced();
// Placement successful, fire the output
m_OnPlacedSuccessfully.FireOutput( pPortalGun, this );
}
}
// Move to new location
NewLocation( m_vDelayedPosition, m_qDelayedAngles );
SetContextThink( &CProp_Portal::TestRestingSurfaceThink, gpGlobals->curtime + 0.1f, s_pTestRestingSurfaceContext );
}
//-----------------------------------------------------------------------------
// Purpose: When placed on a surface that could potentially go away (anything but world geo), we test for that condition and fizzle
//-----------------------------------------------------------------------------
void CProp_Portal::TestRestingSurfaceThink( void )
{
// Make sure there's still a surface behind the portal
Vector vOrigin = GetAbsOrigin();
Vector vForward, vRight, vUp;
GetVectors( &vForward, &vRight, &vUp );
trace_t tr;
CTraceFilterSimpleClassnameList baseFilter( NULL, COLLISION_GROUP_NONE );
UTIL_Portal_Trace_Filter( &baseFilter );
baseFilter.AddClassnameToIgnore( "prop_portal" );
CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
int iCornersOnVolatileSurface = 0;
// Check corners
for ( int iCorner = 0; iCorner < 4; ++iCorner )
{
Vector vCorner = vOrigin;
if ( iCorner % 2 == 0 )
vCorner += vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS * 1.1f );
else
vCorner += -vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS * 1.1f );
if ( iCorner < 2 )
vCorner += vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS * 1.1f );
else
vCorner += -vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS * 1.1f );
Ray_t ray;
ray.Init( vCorner, vCorner - vForward );
enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilterPortalShot, &tr );
// This corner isn't on a valid brush (skipping phys converts or physboxes because they frequently go through portals and can't be placed upon).
if ( tr.fraction == 1.0f && !tr.startsolid && ( !tr.m_pEnt || ( tr.m_pEnt && !FClassnameIs( tr.m_pEnt, "func_physbox" ) && !FClassnameIs( tr.m_pEnt, "simple_physics_brush" ) ) ) )
{
DevMsg( "Surface removed from behind portal.\n" );
Fizzle();
SetContextThink( NULL, TICK_NEVER_THINK, s_pTestRestingSurfaceContext );
break;
}
if ( !tr.DidHitWorld() )
{
iCornersOnVolatileSurface++;
}
}
// Still on a movable or deletable surface
if ( iCornersOnVolatileSurface > 0 )
{
SetContextThink ( &CProp_Portal::TestRestingSurfaceThink, gpGlobals->curtime + 0.1f, s_pTestRestingSurfaceContext );
}
else
{
// All corners on world, we don't need to test
SetContextThink( NULL, TICK_NEVER_THINK, s_pTestRestingSurfaceContext );
}
}
bool CProp_Portal::IsActivedAndLinked( void ) const
{
return ( m_bActivated && m_hLinkedPortal.Get() != NULL );
}
void CProp_Portal::ResetModel( void )
{
if( !m_bIsPortal2 )
SetModel( "models/portals/portal1.mdl" );
else
SetModel( "models/portals/portal2.mdl" );
SetSize( CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs );
SetSolid( SOLID_OBB );
SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID | FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
}
void CProp_Portal::DoFizzleEffect( int iEffect, bool bDelayedPos /*= true*/ )
{
m_vAudioOrigin = ( ( bDelayedPos ) ? ( m_vDelayedPosition ) : ( GetAbsOrigin() ) );
CEffectData fxData;
fxData.m_vAngles = ( ( bDelayedPos ) ? ( m_qDelayedAngles ) : ( GetAbsAngles() ) );
Vector vForward, vUp;
AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
fxData.m_vOrigin = m_vAudioOrigin + vForward * 1.0f;
fxData.m_nColor = ( ( m_bIsPortal2 ) ? ( 1 ) : ( 0 ) );
EmitSound_t ep;
CPASAttenuationFilter filter( m_vDelayedPosition );
ep.m_nChannel = CHAN_STATIC;
ep.m_flVolume = 1.0f;
ep.m_pOrigin = &m_vAudioOrigin;
// Rumble effects on the firing player (if one exists)
CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
if ( pPortalGun && (iEffect != PORTAL_FIZZLE_CLOSE )
&& (iEffect != PORTAL_FIZZLE_SUCCESS )
&& (iEffect != PORTAL_FIZZLE_NONE ) )
{
CBasePlayer* pPlayer = (CBasePlayer*)pPortalGun->GetOwner();
if ( pPlayer )
{
pPlayer->RumbleEffect( RUMBLE_PORTAL_PLACEMENT_FAILURE, 0, RUMBLE_FLAGS_NONE );
}
}
// Pick a fizzle effect
switch ( iEffect )
{
case PORTAL_FIZZLE_CANT_FIT:
//DispatchEffect( "PortalFizzleCantFit", fxData );
//ep.m_pSoundName = "Portal.fizzle_invalid_surface";
VectorAngles( vUp, vForward, fxData.m_vAngles );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_nofit" ) : ( "portal_1_nofit" ) ), fxData.m_vOrigin, fxData.m_vAngles, this );
break;
case PORTAL_FIZZLE_OVERLAPPED_LINKED:
{
/*CProp_Portal *pLinkedPortal = m_hLinkedPortal;
if ( pLinkedPortal )
{
Vector vLinkedForward;
pLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
fxData.m_vStart = pLink3edPortal->GetAbsOrigin() + vLinkedForward * 5.0f;
}*/
//DispatchEffect( "PortalFizzleOverlappedLinked", fxData );
VectorAngles( vUp, vForward, fxData.m_vAngles );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_overlap" ) : ( "portal_1_overlap" ) ), fxData.m_vOrigin, fxData.m_vAngles, this );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
break;
}
case PORTAL_FIZZLE_BAD_VOLUME:
//DispatchEffect( "PortalFizzleBadVolume", fxData );
VectorAngles( vUp, vForward, fxData.m_vAngles );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_badvolume" ) : ( "portal_1_badvolume" ) ), fxData.m_vOrigin, fxData.m_vAngles );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
break;
case PORTAL_FIZZLE_BAD_SURFACE:
//DispatchEffect( "PortalFizzleBadSurface", fxData );
VectorAngles( vUp, vForward, fxData.m_vAngles );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_badsurface" ) : ( "portal_1_badsurface" ) ), fxData.m_vOrigin, fxData.m_vAngles );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
break;
case PORTAL_FIZZLE_KILLED:
//DispatchEffect( "PortalFizzleKilled", fxData );
VectorAngles( vUp, vForward, fxData.m_vAngles );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_close" ) : ( "portal_1_close" ) ), fxData.m_vOrigin, fxData.m_vAngles );
ep.m_pSoundName = "Portal.fizzle_moved";
break;
case PORTAL_FIZZLE_CLEANSER:
//DispatchEffect( "PortalFizzleCleanser", fxData );
VectorAngles( vUp, vForward, fxData.m_vAngles );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_cleanser" ) : ( "portal_1_cleanser" ) ), fxData.m_vOrigin, fxData.m_vAngles, this );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
break;
case PORTAL_FIZZLE_CLOSE:
//DispatchEffect( "PortalFizzleKilled", fxData );
VectorAngles( vUp, vForward, fxData.m_vAngles );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_close" ) : ( "portal_1_close" ) ), fxData.m_vOrigin, fxData.m_vAngles );
ep.m_pSoundName = ( ( m_bIsPortal2 ) ? ( "Portal.close_red" ) : ( "Portal.close_blue" ) );
break;
case PORTAL_FIZZLE_NEAR_BLUE:
{
if ( !m_bIsPortal2 )
{
Vector vLinkedForward;
m_hLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
fxData.m_vOrigin = m_hLinkedPortal->GetAbsOrigin() + vLinkedForward * 16.0f;
fxData.m_vAngles = m_hLinkedPortal->GetAbsAngles();
}
else
{
GetVectors( &vForward, NULL, NULL );
fxData.m_vOrigin = GetAbsOrigin() + vForward * 16.0f;
fxData.m_vAngles = GetAbsAngles();
}
//DispatchEffect( "PortalFizzleNear", fxData );
AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
VectorAngles( vUp, vForward, fxData.m_vAngles );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_near" ) : ( "portal_1_near" ) ), fxData.m_vOrigin, fxData.m_vAngles );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
break;
}
case PORTAL_FIZZLE_NEAR_RED:
{
if ( m_bIsPortal2 )
{
Vector vLinkedForward;
m_hLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
fxData.m_vOrigin = m_hLinkedPortal->GetAbsOrigin() + vLinkedForward * 16.0f;
fxData.m_vAngles = m_hLinkedPortal->GetAbsAngles();
}
else
{
GetVectors( &vForward, NULL, NULL );
fxData.m_vOrigin = GetAbsOrigin() + vForward * 16.0f;
fxData.m_vAngles = GetAbsAngles();
}
//DispatchEffect( "PortalFizzleNear", fxData );
AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
VectorAngles( vUp, vForward, fxData.m_vAngles );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_near" ) : ( "portal_1_near" ) ), fxData.m_vOrigin, fxData.m_vAngles );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
break;
}
case PORTAL_FIZZLE_SUCCESS:
VectorAngles( vUp, vForward, fxData.m_vAngles );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_success" ) : ( "portal_1_success" ) ), fxData.m_vOrigin, fxData.m_vAngles );
// Don't make a sound!
return;
case PORTAL_FIZZLE_NONE:
// Don't do anything!
return;
}
EmitSound( filter, SOUND_FROM_WORLD, ep );
}
//-----------------------------------------------------------------------------
// Purpose: Fizzle the portal
//-----------------------------------------------------------------------------
void CProp_Portal::FizzleThink( void )
{
CProp_Portal *pRemotePortal = m_hLinkedPortal;
RemovePortalMicAndSpeaker();
if ( m_pAmbientSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundChangeVolume( m_pAmbientSound, 0.0, 0.0 );
}
StopParticleEffects( this );
m_bActivated = false;
m_hLinkedPortal = NULL;
m_PortalSimulator.DetachFromLinked();
m_PortalSimulator.ReleaseAllEntityOwnership();
if( pRemotePortal )
{
//pRemotePortal->m_hLinkedPortal = NULL;
pRemotePortal->UpdatePortalLinkage();
}
SetContextThink( NULL, TICK_NEVER_THINK, s_pFizzleThink );
}
//-----------------------------------------------------------------------------
// Purpose: Portal will fizzle next time we get to think
//-----------------------------------------------------------------------------
void CProp_Portal::Fizzle( void )
{
SetContextThink( &CProp_Portal::FizzleThink, gpGlobals->curtime, s_pFizzleThink );
}
//-----------------------------------------------------------------------------
// Purpose: Removes the portal microphone and speakers. This is done in two places
// (fizzle and UpdateOnRemove) so the code is consolidated here.
// Input : -
//-----------------------------------------------------------------------------
void CProp_Portal::RemovePortalMicAndSpeaker()
{
// Shut down microphone/speaker if they exist
if ( m_hMicrophone )
{
CEnvMicrophone *pMicrophone = (CEnvMicrophone*)(m_hMicrophone.Get());
if ( pMicrophone )
{
inputdata_t in;
pMicrophone->InputDisable( in );
UTIL_Remove( pMicrophone );
}
m_hMicrophone = 0;
}
if ( m_hSpeaker )
{
CSpeaker *pSpeaker = (CSpeaker *)(m_hSpeaker.Get());
if ( pSpeaker )
{
// Remove the remote portal's microphone, as it references the speaker we're about to remove.
if ( m_hLinkedPortal.Get() )
{
CProp_Portal* pRemotePortal = m_hLinkedPortal.Get();
if ( pRemotePortal->m_hMicrophone )
{
inputdata_t inputdata;
inputdata.pActivator = this;
inputdata.pCaller = this;
CEnvMicrophone* pRemotePortalMic = dynamic_cast<CEnvMicrophone*>(pRemotePortal->m_hMicrophone.Get());
if ( pRemotePortalMic )
{
pRemotePortalMic->Remove();
}
}
}
inputdata_t in;
pSpeaker->InputTurnOff( in );
UTIL_Remove( pSpeaker );
}
m_hSpeaker = 0;
}
}
void CProp_Portal::PunchPenetratingPlayer( CBaseEntity *pPlayer )
{
if( m_PortalSimulator.IsReadyToSimulate() )
{
ICollideable *pCollideable = pPlayer->GetCollideable();
if ( pCollideable )
{
Vector vMin, vMax;
pCollideable->WorldSpaceSurroundingBounds( &vMin, &vMax );
if ( UTIL_IsBoxIntersectingPortal( ( vMin + vMax ) / 2.0f, ( vMax - vMin ) / 2.0f, this ) )
{
Vector vForward;
GetVectors( &vForward, 0, 0 );
vForward *= 100.0f;
pPlayer->VelocityPunch( vForward );
}
}
}
}
void CProp_Portal::PunchAllPenetratingPlayers( void )
{
for( int i = 1; i <= gpGlobals->maxClients; ++i )
{
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
if( pPlayer )
PunchPenetratingPlayer( pPlayer );
}
}
void CProp_Portal::Activate( void )
{
if( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) == -1 )
s_PortalLinkageGroups[m_iLinkageGroupID].AddToTail( this );
if( m_pAttachedCloningArea == NULL )
m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
UpdatePortalTeleportMatrix();
UpdatePortalLinkage();
BaseClass::Activate();
CreateSounds();
AddEffects( EF_NOSHADOW | EF_NORECEIVESHADOW );
if( m_bActivated && (m_hLinkedPortal.Get() != NULL) )
{
Vector ptCenter = GetAbsOrigin();
QAngle qAngles = GetAbsAngles();
m_PortalSimulator.MoveTo( ptCenter, qAngles );
//resimulate everything we're touching
touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
if( root )
{
for( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
{
CBaseEntity *pOther = link->entityTouched;
if( CProp_Portal_Shared::IsEntityTeleportable( pOther ) )
{
CCollisionProperty *pOtherCollision = pOther->CollisionProp();
Vector vWorldMins, vWorldMaxs;
pOtherCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
Vector ptOtherCenter = (vWorldMins + vWorldMaxs) / 2.0f;
if( m_plane_Origin.normal.Dot( ptOtherCenter ) > m_plane_Origin.dist )
{
//we should be interacting with this object, add it to our environment
if( SharedEnvironmentCheck( pOther ) )
{
Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
(m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
m_PortalSimulator.TakeOwnershipOfEntity( pOther );
}
}
}
}
}
}
}
bool CProp_Portal::ShouldTeleportTouchingEntity( CBaseEntity *pOther )
{
if( !m_PortalSimulator.OwnsEntity( pOther ) ) //can't teleport an entity we don't own
{
#if !defined ( DISABLE_DEBUG_HISTORY )
if ( !IsMarkedForDeletion() )
{
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i not teleporting %s because it's not simulated by this portal.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() ) );
}
#endif
if ( sv_portal_debug_touch.GetBool() )
{
Msg( "Portal %i not teleporting %s because it's not simulated by this portal. : %f \n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName(), gpGlobals->curtime );
}
return false;
}
if( !CProp_Portal_Shared::IsEntityTeleportable( pOther ) )
return false;
if( m_hLinkedPortal.Get() == NULL )
{
#if !defined ( DISABLE_DEBUG_HISTORY )
if ( !IsMarkedForDeletion() )
{
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i not teleporting %s because it has no linked partner portal.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() ) );
}
#endif
if ( sv_portal_debug_touch.GetBool() )
{
Msg( "Portal %i not teleporting %s because it has no linked partner portal.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() );
}
return false;
}
//Vector ptOtherOrigin = pOther->GetAbsOrigin();
Vector ptOtherCenter = pOther->WorldSpaceCenter();
IPhysicsObject *pOtherPhysObject = pOther->VPhysicsGetObject();
Vector vOtherVelocity;
//grab current velocity
{
if( sv_portal_new_velocity_check.GetBool() )
{
//we're assuming that physics velocity is the most reliable of all if the convar is true
if( pOtherPhysObject )
{
//pOtherPhysObject->GetImplicitVelocity( &vOtherVelocity, NULL );
pOtherPhysObject->GetVelocity( &vOtherVelocity, NULL );
if( vOtherVelocity == vec3_origin )
{
pOther->GetVelocity( &vOtherVelocity );
}
}
else
{
pOther->GetVelocity( &vOtherVelocity );
}
}
else
{
//old style of velocity grabbing, which uses implicit velocity as a last resort
if( pOther->GetMoveType() == MOVETYPE_VPHYSICS )
{
if( pOtherPhysObject && (pOtherPhysObject->GetShadowController() == NULL) )
pOtherPhysObject->GetVelocity( &vOtherVelocity, NULL );
else
pOther->GetVelocity( &vOtherVelocity );
}
else
{
pOther->GetVelocity( &vOtherVelocity );
}
if( vOtherVelocity == vec3_origin )
{
// Recorded velocity is sometimes zero under pushed or teleported movement, or after position correction.
// In these circumstances, we want implicit velocity ((last pos - this pos) / timestep )
if ( pOtherPhysObject )
{
Vector vOtherImplicitVelocity;
pOtherPhysObject->GetImplicitVelocity( &vOtherImplicitVelocity, NULL );
vOtherVelocity += vOtherImplicitVelocity;
}
}
}
}
// Test for entity's center being past portal plane
if(m_PortalSimulator.m_DataAccess.Placement.PortalPlane.m_Normal.Dot( ptOtherCenter ) < m_PortalSimulator.m_DataAccess.Placement.PortalPlane.m_Dist)
{
//entity wants to go further into the plane
if( m_PortalSimulator.EntityIsInPortalHole( pOther ) )
{
#ifdef _DEBUG
static int iAntiRecurse = 0;
if( pOther->IsPlayer() && (iAntiRecurse == 0) )
{
++iAntiRecurse;
ShouldTeleportTouchingEntity( pOther ); //do it again for debugging
--iAntiRecurse;
}
#endif
return true;
}
else
{
#if !defined ( DISABLE_DEBUG_HISTORY )
if ( !IsMarkedForDeletion() )
{
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i not teleporting %s because it was not in the portal hole.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() ) );
}
#endif
if ( sv_portal_debug_touch.GetBool() )
{
Msg( "Portal %i not teleporting %s because it was not in the portal hole.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() );
}
}
}
return false;
}
void CProp_Portal::TeleportTouchingEntity( CBaseEntity *pOther )
{
if ( GetPortalCallQueue() )
{
GetPortalCallQueue()->QueueCall( this, &CProp_Portal::TeleportTouchingEntity, pOther );
return;
}
Assert( m_hLinkedPortal.Get() != NULL );
Vector ptOtherOrigin = pOther->GetAbsOrigin();
Vector ptOtherCenter;
bool bPlayer = pOther->IsPlayer();
QAngle qPlayerEyeAngles;
CPortal_Player *pOtherAsPlayer;
if( bPlayer )
{
//NDebugOverlay::EntityBounds( pOther, 255, 0, 0, 128, 60.0f );
pOtherAsPlayer = (CPortal_Player *)pOther;
qPlayerEyeAngles = pOtherAsPlayer->pl.v_angle;
}
else
{
pOtherAsPlayer = NULL;
}
ptOtherCenter = pOther->WorldSpaceCenter();
bool bNonPhysical = false; //special case handling for non-physical objects such as the energy ball and player
QAngle qOtherAngles;
Vector vOtherVelocity;
//grab current velocity
{
IPhysicsObject *pOtherPhysObject = pOther->VPhysicsGetObject();
if( pOther->GetMoveType() == MOVETYPE_VPHYSICS )
{
if( pOtherPhysObject && (pOtherPhysObject->GetShadowController() == NULL) )
pOtherPhysObject->GetVelocity( &vOtherVelocity, NULL );
else
pOther->GetVelocity( &vOtherVelocity );
}
else if ( bPlayer && pOther->VPhysicsGetObject() )
{
pOther->VPhysicsGetObject()->GetVelocity( &vOtherVelocity, NULL );
if ( vOtherVelocity == vec3_origin )
{
vOtherVelocity = pOther->GetAbsVelocity();
}
}
else
{
pOther->GetVelocity( &vOtherVelocity );
}
if( vOtherVelocity == vec3_origin )
{
// Recorded velocity is sometimes zero under pushed or teleported movement, or after position correction.
// In these circumstances, we want implicit velocity ((last pos - this pos) / timestep )
if ( pOtherPhysObject )
{
Vector vOtherImplicitVelocity;
pOtherPhysObject->GetImplicitVelocity( &vOtherImplicitVelocity, NULL );
vOtherVelocity += vOtherImplicitVelocity;
}
}
}
const PS_InternalData_t &RemotePortalDataAccess = m_hLinkedPortal->m_PortalSimulator.m_DataAccess;
const PS_InternalData_t &LocalPortalDataAccess = m_PortalSimulator.m_DataAccess;
if( bPlayer )
{
qOtherAngles = pOtherAsPlayer->EyeAngles();
pOtherAsPlayer->m_qPrePortalledViewAngles = qOtherAngles;
pOtherAsPlayer->m_bFixEyeAnglesFromPortalling = true;
pOtherAsPlayer->m_matLastPortalled = m_matrixThisToLinked;
bNonPhysical = true;
//if( (fabs( RemotePortalDataAccess.Placement.vForward.z ) + fabs( LocalPortalDataAccess.Placement.vForward.z )) > 0.7071f ) //some combination of floor/ceiling
if( fabs( LocalPortalDataAccess.Placement.vForward.z ) > 0.0f )
{
//we may have to compensate for the fact that AABB's don't rotate ever
float fAbsLocalZ = fabs( LocalPortalDataAccess.Placement.vForward.z );
float fAbsRemoteZ = fabs( RemotePortalDataAccess.Placement.vForward.z );
if( (fabs(fAbsLocalZ - 1.0f) < 0.01f) &&
(fabs(fAbsRemoteZ - 1.0f) < 0.01f) )
//(fabs( LocalPortalDataAccess.Placement.vForward.z + RemotePortalDataAccess.Placement.vForward.z ) < 0.01f) )
{
//portals are both aligned on the z axis, no need to shrink the player
}
else
{
//curl the player up into a little ball
pOtherAsPlayer->SetGroundEntity( NULL );
if( !pOtherAsPlayer->IsDucked() )
{
pOtherAsPlayer->ForceDuckThisFrame();
pOtherAsPlayer->m_Local.m_bInDuckJump = true;
if( LocalPortalDataAccess.Placement.vForward.z > 0.0f )
ptOtherCenter.z -= 16.0f; //portal facing up, shrink downwards
else
ptOtherCenter.z += 16.0f; //portal facing down, shrink upwards
}
}
}
}
else
{
qOtherAngles = pOther->GetAbsAngles();
bNonPhysical = FClassnameIs( pOther, "prop_energy_ball" );
}
Vector ptNewOrigin;
QAngle qNewAngles;
Vector vNewVelocity;
//apply transforms to relevant variables (applied to the entity later)
{
if( bPlayer )
{
ptNewOrigin = m_matrixThisToLinked * ptOtherCenter;
ptNewOrigin += ptOtherOrigin - ptOtherCenter;
}
else
{
ptNewOrigin = m_matrixThisToLinked * ptOtherOrigin;
}
// Reorient object angles, originally we did a transformation on the angles, but that doesn't quite work right for gimbal lock cases
qNewAngles = TransformAnglesToWorldSpace( qOtherAngles, m_matrixThisToLinked.As3x4() );
qNewAngles.x = AngleNormalizePositive( qNewAngles.x );
qNewAngles.y = AngleNormalizePositive( qNewAngles.y );
qNewAngles.z = AngleNormalizePositive( qNewAngles.z );
// Reorient the velocity
vNewVelocity = m_matrixThisToLinked.ApplyRotation( vOtherVelocity );
}
//help camera reorientation for the player
if( bPlayer )
{
Vector vPlayerForward;
AngleVectors( qOtherAngles, &vPlayerForward, NULL, NULL );
float fPlayerForwardZ = vPlayerForward.z;
vPlayerForward.z = 0.0f;
float fForwardLength = vPlayerForward.Length();
if ( fForwardLength > 0.0f )
{
VectorNormalize( vPlayerForward );
}
float fPlayerFaceDotPortalFace = LocalPortalDataAccess.Placement.vForward.Dot( vPlayerForward );
float fPlayerFaceDotPortalUp = LocalPortalDataAccess.Placement.vUp.Dot( vPlayerForward );
CBaseEntity *pHeldEntity = GetPlayerHeldEntity( pOtherAsPlayer );
// Sometimes reorienting by pitch is more desirable than by roll depending on the portals' orientations and the relative player facing direction
if ( pHeldEntity ) // never pitch reorient while holding an object
{
pOtherAsPlayer->m_bPitchReorientation = false;
}
else if ( LocalPortalDataAccess.Placement.vUp.z > 0.99f && // entering wall portal
( fForwardLength == 0.0f || // facing strait up or down
fPlayerFaceDotPortalFace > 0.5f || // facing mostly away from portal
fPlayerFaceDotPortalFace < -0.5f ) // facing mostly toward portal
)
{
pOtherAsPlayer->m_bPitchReorientation = true;
}
else if ( ( LocalPortalDataAccess.Placement.vForward.z > 0.99f || LocalPortalDataAccess.Placement.vForward.z < -0.99f ) && // entering floor or ceiling portal
( RemotePortalDataAccess.Placement.vForward.z > 0.99f || RemotePortalDataAccess.Placement.vForward.z < -0.99f ) && // exiting floor or ceiling portal
( fPlayerForwardZ < -0.5f || fPlayerForwardZ > 0.5f ) // facing mustly up or down
)
{
pOtherAsPlayer->m_bPitchReorientation = true;
}
else if ( ( RemotePortalDataAccess.Placement.vForward.z > 0.75f && RemotePortalDataAccess.Placement.vForward.z <= 0.99f ) && // exiting wedge portal
( fPlayerFaceDotPortalUp > 0.0f ) // facing toward the top of the portal
)
{
pOtherAsPlayer->m_bPitchReorientation = true;
}
else
{
pOtherAsPlayer->m_bPitchReorientation = false;
}
}
//velocity hacks
{
//minimum floor exit velocity if both portals are on the floor or the player is coming out of the floor
if( RemotePortalDataAccess.Placement.vForward.z > 0.7071f )
{
if ( bPlayer )
{
if( vNewVelocity.z < MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY_PLAYER )
vNewVelocity.z = MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY_PLAYER;
}
else
{
if( LocalPortalDataAccess.Placement.vForward.z > 0.7071f )
{
if( vNewVelocity.z < MINIMUM_FLOOR_TO_FLOOR_PORTAL_EXIT_VELOCITY )
vNewVelocity.z = MINIMUM_FLOOR_TO_FLOOR_PORTAL_EXIT_VELOCITY;
}
else
{
if( vNewVelocity.z < MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY )
vNewVelocity.z = MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY;
}
}
}
if ( vNewVelocity.LengthSqr() > (MAXIMUM_PORTAL_EXIT_VELOCITY * MAXIMUM_PORTAL_EXIT_VELOCITY) )
vNewVelocity *= (MAXIMUM_PORTAL_EXIT_VELOCITY / vNewVelocity.Length());
}
//untouch the portal(s), will force a touch on destination after the teleport
{
m_PortalSimulator.ReleaseOwnershipOfEntity( pOther, true );
this->PhysicsNotifyOtherOfUntouch( this, pOther );
pOther->PhysicsNotifyOtherOfUntouch( pOther, this );
m_hLinkedPortal->m_PortalSimulator.TakeOwnershipOfEntity( pOther );
//m_hLinkedPortal->PhysicsNotifyOtherOfUntouch( m_hLinkedPortal, pOther );
//pOther->PhysicsNotifyOtherOfUntouch( pOther, m_hLinkedPortal );
}
if( sv_portal_debug_touch.GetBool() )
{
DevMsg( "PORTAL %i TELEPORTING: %s\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname() );
}
#if !defined ( DISABLE_DEBUG_HISTORY )
if ( !IsMarkedForDeletion() )
{
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "PORTAL %i TELEPORTING: %s\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname() ) );
}
#endif
//do the actual teleportation
{
pOther->SetGroundEntity( NULL );
if( bPlayer )
{
QAngle qTransformedEyeAngles = TransformAnglesToWorldSpace( qPlayerEyeAngles, m_matrixThisToLinked.As3x4() );
qTransformedEyeAngles.x = AngleNormalizePositive( qTransformedEyeAngles.x );
qTransformedEyeAngles.y = AngleNormalizePositive( qTransformedEyeAngles.y );
qTransformedEyeAngles.z = AngleNormalizePositive( qTransformedEyeAngles.z );
pOtherAsPlayer->pl.v_angle = qTransformedEyeAngles;
pOtherAsPlayer->pl.fixangle = FIXANGLE_ABSOLUTE;
pOtherAsPlayer->UpdateVPhysicsPosition( ptNewOrigin, vNewVelocity, 0.0f );
pOtherAsPlayer->Teleport( &ptNewOrigin, &qNewAngles, &vNewVelocity );
//pOtherAsPlayer->UnDuck();
//pOtherAsPlayer->m_angEyeAngles = qTransformedEyeAngles;
//pOtherAsPlayer->pl.v_angle = qTransformedEyeAngles;
//pOtherAsPlayer->pl.fixangle = FIXANGLE_ABSOLUTE;
}
else
{
if( bNonPhysical )
{
pOther->Teleport( &ptNewOrigin, &qNewAngles, &vNewVelocity );
}
else
{
//doing velocity in two stages as a bug workaround, setting the velocity to anything other than 0 will screw up how objects rest on this entity in the future
pOther->Teleport( &ptNewOrigin, &qNewAngles, &vec3_origin );
pOther->ApplyAbsVelocityImpulse( vNewVelocity );
}
}
}
IPhysicsObject *pPhys = pOther->VPhysicsGetObject();
if( (pPhys != NULL) && (pPhys->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
{
CPortal_Player *pHoldingPlayer = (CPortal_Player *)GetPlayerHoldingEntity( pOther );
pHoldingPlayer->ToggleHeldObjectOnOppositeSideOfPortal();
if ( pHoldingPlayer->IsHeldObjectOnOppositeSideOfPortal() )
pHoldingPlayer->SetHeldObjectPortal( this );
else
pHoldingPlayer->SetHeldObjectPortal( NULL );
}
else if( bPlayer )
{
CBaseEntity *pHeldEntity = GetPlayerHeldEntity( pOtherAsPlayer );
if( pHeldEntity )
{
pOtherAsPlayer->ToggleHeldObjectOnOppositeSideOfPortal();
if( pOtherAsPlayer->IsHeldObjectOnOppositeSideOfPortal() )
{
pOtherAsPlayer->SetHeldObjectPortal( m_hLinkedPortal );
}
else
{
pOtherAsPlayer->SetHeldObjectPortal( NULL );
//we need to make sure the held object and player don't interpenetrate when the player's shape changes
Vector vTargetPosition;
QAngle qTargetOrientation;
UpdateGrabControllerTargetPosition( pOtherAsPlayer, &vTargetPosition, &qTargetOrientation );
pHeldEntity->Teleport( &vTargetPosition, &qTargetOrientation, 0 );
FindClosestPassableSpace( pHeldEntity, RemotePortalDataAccess.Placement.vForward );
}
}
//we haven't found a good way of fixing the problem of "how do you reorient an AABB". So we just move the player so that they fit
//m_hLinkedPortal->ForceEntityToFitInPortalWall( pOtherAsPlayer );
}
//force the entity to be touching the other portal right this millisecond
{
trace_t Trace;
memset( &Trace, 0, sizeof(trace_t) );
//UTIL_TraceEntity( pOther, ptNewOrigin, ptNewOrigin, MASK_SOLID, pOther, COLLISION_GROUP_NONE, &Trace ); //fires off some asserts, and we just need a dummy anyways
pOther->PhysicsMarkEntitiesAsTouching( m_hLinkedPortal.Get(), Trace );
m_hLinkedPortal.Get()->PhysicsMarkEntitiesAsTouching( pOther, Trace );
}
// Notify the entity that it's being teleported
// Tell the teleported entity of the portal it has just arrived at
notify_teleport_params_t paramsTeleport;
paramsTeleport.prevOrigin = ptOtherOrigin;
paramsTeleport.prevAngles = qOtherAngles;
paramsTeleport.physicsRotate = true;
notify_system_event_params_t eventParams ( &paramsTeleport );
pOther->NotifySystemEvent( this, NOTIFY_EVENT_TELEPORT, eventParams );
//notify clients of the teleportation
{
CBroadcastRecipientFilter filter;
filter.MakeReliable();
UserMessageBegin( filter, "EntityPortalled" );
WRITE_EHANDLE( this );
WRITE_EHANDLE( pOther );
WRITE_FLOAT( ptNewOrigin.x );
WRITE_FLOAT( ptNewOrigin.y );
WRITE_FLOAT( ptNewOrigin.z );
WRITE_FLOAT( qNewAngles.x );
WRITE_FLOAT( qNewAngles.y );
WRITE_FLOAT( qNewAngles.z );
MessageEnd();
}
#ifdef _DEBUG
{
Vector ptTestCenter = pOther->WorldSpaceCenter();
float fNewDist, fOldDist;
fNewDist = RemotePortalDataAccess.Placement.PortalPlane.m_Normal.Dot( ptTestCenter ) - RemotePortalDataAccess.Placement.PortalPlane.m_Dist;
fOldDist = LocalPortalDataAccess.Placement.PortalPlane.m_Normal.Dot( ptOtherCenter ) - LocalPortalDataAccess.Placement.PortalPlane.m_Dist;
AssertMsg( fNewDist >= 0.0f, "Entity portalled behind the destination portal." );
}
#endif
pOther->NetworkProp()->NetworkStateForceUpdate();
if( bPlayer )
pOtherAsPlayer->pl.NetworkStateChanged();
//if( bPlayer )
// NDebugOverlay::EntityBounds( pOther, 0, 255, 0, 128, 60.0f );
Assert( (bPlayer == false) || (pOtherAsPlayer->m_hPortalEnvironment.Get() == m_hLinkedPortal.Get()) );
}
void CProp_Portal::Touch( CBaseEntity *pOther )
{
BaseClass::Touch( pOther );
pOther->Touch( this );
// Don't do anything on touch if it's not active
if( !m_bActivated || (m_hLinkedPortal.Get() == NULL) )
{
Assert( !m_PortalSimulator.OwnsEntity( pOther ) );
Assert( !pOther->IsPlayer() || (((CPortal_Player *)pOther)->m_hPortalEnvironment.Get() != this) );
//I'd really like to fix the root cause, but this will keep the game going
m_PortalSimulator.ReleaseOwnershipOfEntity( pOther );
return;
}
Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
(m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
// Fizzle portal with any moving brush
Vector vVelocityCheck;
AngularImpulse vAngularImpulseCheck;
pOther->GetVelocity( &vVelocityCheck, &vAngularImpulseCheck );
if( vVelocityCheck != vec3_origin || vAngularImpulseCheck != vec3_origin )
{
if ( modelinfo->GetModelType( pOther->GetModel() ) == mod_brush )
{
if ( !FClassnameIs( pOther, "func_physbox" ) && !FClassnameIs( pOther, "simple_physics_brush" ) ) // except CPhysBox
{
Vector vForward;
GetVectors( &vForward, NULL, NULL );
Vector vMin, vMax;
pOther->GetCollideable()->WorldSpaceSurroundingBounds( &vMin, &vMax );
if ( UTIL_IsBoxIntersectingPortal( ( vMin + vMax ) / 2.0f, ( vMax - vMin ) / 2.0f - Vector( 2.0f, 2.0f, 2.0f ), this, 0.0f ) )
{
DevMsg( "Moving brush intersected portal plane.\n" );
DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
Fizzle();
}
else
{
Vector vOrigin = GetAbsOrigin();
trace_t tr;
UTIL_TraceLine( vOrigin, vOrigin - vForward * PORTAL_HALF_DEPTH, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
// Something went wrong
if ( tr.fraction == 1.0f && !tr.startsolid )
{
DevMsg( "Surface removed from behind portal.\n" );
DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
Fizzle();
}
else if ( tr.m_pEnt && tr.m_pEnt->IsMoving() )
{
DevMsg( "Surface behind portal is moving.\n" );
DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
Fizzle();
}
}
}
}
}
if( m_hLinkedPortal == NULL )
return;
//see if we should even be interacting with this object, this is a bugfix where some objects get added to physics environments through walls
if( !m_PortalSimulator.OwnsEntity( pOther ) )
{
//hmm, not in our environment, plane tests, sharing tests
if( SharedEnvironmentCheck( pOther ) )
{
bool bObjectCenterInFrontOfPortal = (m_plane_Origin.normal.Dot( pOther->WorldSpaceCenter() ) > m_plane_Origin.dist);
bool bIsStuckPlayer = ( pOther->IsPlayer() )? ( !UTIL_IsSpaceEmpty( pOther, pOther->WorldAlignMins(), pOther->WorldAlignMaxs() ) ) : ( false );
if ( bIsStuckPlayer )
{
Assert ( !"Player stuck" );
DevMsg( "Player in solid behind behind portal %i's plane, Adding to it's environment to run find closest passable space.\n", ((m_bIsPortal2)?(2):(1)) );
}
if ( bObjectCenterInFrontOfPortal || bIsStuckPlayer )
{
if( sv_portal_debug_touch.GetBool() )
{
DevMsg( "Portal %i took control of shared object: %s\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname() );
}
#if !defined ( DISABLE_DEBUG_HISTORY )
if ( !IsMarkedForDeletion() )
{
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i took control of shared object: %s\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname() ) );
}
#endif
//we should be interacting with this object, add it to our environment
CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
m_PortalSimulator.TakeOwnershipOfEntity( pOther );
}
}
else
{
return; //we shouldn't interact with this object
}
}
if( ShouldTeleportTouchingEntity( pOther ) )
TeleportTouchingEntity( pOther );
}
void CProp_Portal::StartTouch( CBaseEntity *pOther )
{
BaseClass::StartTouch( pOther );
// Since prop_portal is a trigger it doesn't send back start touch, so I'm forcing it
pOther->StartTouch( this );
if( sv_portal_debug_touch.GetBool() )
{
DevMsg( "Portal %i StartTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime );
}
#if !defined ( DISABLE_DEBUG_HISTORY )
if ( !IsMarkedForDeletion() )
{
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i StartTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime ) );
}
#endif
if( (m_hLinkedPortal == NULL) || (m_bActivated == false) )
return;
if( CProp_Portal_Shared::IsEntityTeleportable( pOther ) )
{
CCollisionProperty *pOtherCollision = pOther->CollisionProp();
Vector vWorldMins, vWorldMaxs;
pOtherCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
Vector ptOtherCenter = (vWorldMins + vWorldMaxs) / 2.0f;
if( m_plane_Origin.normal.Dot( ptOtherCenter ) > m_plane_Origin.dist )
{
//we should be interacting with this object, add it to our environment
if( SharedEnvironmentCheck( pOther ) )
{
Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
(m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
m_PortalSimulator.TakeOwnershipOfEntity( pOther );
}
}
}
}
void CProp_Portal::EndTouch( CBaseEntity *pOther )
{
BaseClass::EndTouch( pOther );
// Since prop_portal is a trigger it doesn't send back end touch, so I'm forcing it
pOther->EndTouch( this );
// Don't do anything on end touch if it's not active
if( !m_bActivated )
{
return;
}
if( ShouldTeleportTouchingEntity( pOther ) ) //an object passed through the plane and all the way out of the touch box
TeleportTouchingEntity( pOther );
else if( pOther->IsPlayer() && //player
(m_PortalSimulator.m_DataAccess.Placement.vForward.z < -0.7071f) && //most likely falling out of the portal
(m_PortalSimulator.m_DataAccess.Placement.PortalPlane.m_Normal.Dot( pOther->WorldSpaceCenter() ) < m_PortalSimulator.m_DataAccess.Placement.PortalPlane.m_Dist) && //but behind the portal plane
(((CPortal_Player *)pOther)->m_Local.m_bInDuckJump) ) //while ducking
{
//player has pulled their feet up (moving their center instantaneously) while falling downward out of the portal, send them back (probably only for a frame)
DevMsg( "Player pulled feet above the portal they fell out of, postponing Releasing ownership\n" );
//TeleportTouchingEntity( pOther );
}
else
m_PortalSimulator.ReleaseOwnershipOfEntity( pOther );
if( sv_portal_debug_touch.GetBool() )
{
DevMsg( "Portal %i EndTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime );
}
#if !defined( DISABLE_DEBUG_HISTORY )
if ( !IsMarkedForDeletion() )
{
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i EndTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime ) );
}
#endif
}
void CProp_Portal::PortalSimulator_TookOwnershipOfEntity( CBaseEntity *pEntity )
{
if( pEntity->IsPlayer() )
((CPortal_Player *)pEntity)->m_hPortalEnvironment = this;
}
void CProp_Portal::PortalSimulator_ReleasedOwnershipOfEntity( CBaseEntity *pEntity )
{
if( pEntity->IsPlayer() && (((CPortal_Player *)pEntity)->m_hPortalEnvironment.Get() == this) )
((CPortal_Player *)pEntity)->m_hPortalEnvironment = NULL;
}
bool CProp_Portal::SharedEnvironmentCheck( CBaseEntity *pEntity )
{
Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
(m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pEntity );
if( (pOwningSimulator == NULL) || (pOwningSimulator == &m_PortalSimulator) )
{
//nobody else is claiming ownership
return true;
}
Vector ptCenter = pEntity->WorldSpaceCenter();
if( (ptCenter - m_PortalSimulator.m_DataAccess.Placement.ptCenter).LengthSqr() < (ptCenter - pOwningSimulator->m_DataAccess.Placement.ptCenter).LengthSqr() )
return true;
/*if( !m_hLinkedPortal->m_PortalSimulator.EntityIsInPortalHole( pEntity ) )
{
Vector vOtherVelocity;
pEntity->GetVelocity( &vOtherVelocity );
if( vOtherVelocity.Dot( m_PortalSimulator.m_DataAccess.Placement.vForward ) < vOtherVelocity.Dot( m_hLinkedPortal->m_PortalSimulator.m_DataAccess.Placement.vForward ) )
return true; //entity is going towards this portal more than the other
}*/
return false;
//we're in the shared configuration, and the other portal already owns the object, see if we'd be a better caretaker (distance check
/*CCollisionProperty *pEntityCollision = pEntity->CollisionProp();
Vector vWorldMins, vWorldMaxs;
pEntityCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
Vector ptEntityCenter = (vWorldMins + vWorldMaxs) / 2.0f;
Vector vEntToThis = GetAbsOrigin() - ptEntityCenter;
Vector vEntToRemote = m_hLinkedPortal->GetAbsOrigin() - ptEntityCenter;
return ( vEntToThis.LengthSqr() < vEntToRemote.LengthSqr() );*/
}
void CProp_Portal::WakeNearbyEntities( void )
{
CBaseEntity* pList[ 1024 ];
Vector vForward, vUp, vRight;
GetVectors( &vForward, &vRight, &vUp );
Vector ptOrigin = GetAbsOrigin();
QAngle qAngles = GetAbsAngles();
Vector ptOBBStart = ptOrigin;
ptOBBStart += vForward * CProp_Portal_Shared::vLocalMins.x;
ptOBBStart += vRight * CProp_Portal_Shared::vLocalMins.y;
ptOBBStart += vUp * CProp_Portal_Shared::vLocalMins.z;
vForward *= CProp_Portal_Shared::vLocalMaxs.x - CProp_Portal_Shared::vLocalMins.x;
vRight *= CProp_Portal_Shared::vLocalMaxs.y - CProp_Portal_Shared::vLocalMins.y;
vUp *= CProp_Portal_Shared::vLocalMaxs.z - CProp_Portal_Shared::vLocalMins.z;
Vector vAABBMins, vAABBMaxs;
vAABBMins = vAABBMaxs = ptOBBStart;
for( int i = 1; i != 8; ++i )
{
Vector ptTest = ptOBBStart;
if( i & (1 << 0) ) ptTest += vForward;
if( i & (1 << 1) ) ptTest += vRight;
if( i & (1 << 2) ) ptTest += vUp;
if( ptTest.x < vAABBMins.x ) vAABBMins.x = ptTest.x;
if( ptTest.y < vAABBMins.y ) vAABBMins.y = ptTest.y;
if( ptTest.z < vAABBMins.z ) vAABBMins.z = ptTest.z;
if( ptTest.x > vAABBMaxs.x ) vAABBMaxs.x = ptTest.x;
if( ptTest.y > vAABBMaxs.y ) vAABBMaxs.y = ptTest.y;
if( ptTest.z > vAABBMaxs.z ) vAABBMaxs.z = ptTest.z;
}
int count = UTIL_EntitiesInBox( pList, 1024, vAABBMins, vAABBMaxs, 0 );
//Iterate over all the possible targets
for ( int i = 0; i < count; i++ )
{
CBaseEntity *pEntity = pList[i];
if ( pEntity && (pEntity != this) )
{
CCollisionProperty *pEntCollision = pEntity->CollisionProp();
Vector ptEntityCenter = pEntCollision->GetCollisionOrigin();
//double check intersection at the OBB vs OBB level, we don't want to affect large piles of physics objects if we don't have to. It gets slow
if( IsOBBIntersectingOBB( ptOrigin, qAngles, CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs,
ptEntityCenter, pEntCollision->GetCollisionAngles(), pEntCollision->OBBMins(), pEntCollision->OBBMaxs() ) )
{
if( FClassnameIs( pEntity, "func_portal_detector" ) )
{
// It's a portal detector
CFuncPortalDetector *pPortalDetector = static_cast<CFuncPortalDetector*>( pEntity );
if ( pPortalDetector->IsActive() && pPortalDetector->GetLinkageGroupID() == m_iLinkageGroupID )
{
// It's detecting this portal's group
Vector vMin, vMax;
pPortalDetector->CollisionProp()->WorldSpaceAABB( &vMin, &vMax );
Vector vBoxCenter = ( vMin + vMax ) * 0.5f;
Vector vBoxExtents = ( vMax - vMin ) * 0.5f;
if ( UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, this ) )
{
// It's intersecting this portal
if ( m_bIsPortal2 )
pPortalDetector->m_OnStartTouchPortal2.FireOutput( this, pPortalDetector );
else
pPortalDetector->m_OnStartTouchPortal1.FireOutput( this, pPortalDetector );
if ( IsActivedAndLinked() )
{
pPortalDetector->m_OnStartTouchLinkedPortal.FireOutput( this, pPortalDetector );
if ( UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, m_hLinkedPortal ) )
{
pPortalDetector->m_OnStartTouchBothLinkedPortals.FireOutput( this, pPortalDetector );
}
}
}
}
}
pEntity->WakeRestingObjects();
//pEntity->SetGroundEntity( NULL );
if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
{
IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
if ( pPhysicsObject && pPhysicsObject->IsMoveable() )
{
pPhysicsObject->Wake();
// If the target is debris, convert it to non-debris
if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
{
// Interactive debris converts back to debris when it comes to rest
pEntity->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
}
}
}
}
}
}
}
void CProp_Portal::ForceEntityToFitInPortalWall( CBaseEntity *pEntity )
{
CCollisionProperty *pCollision = pEntity->CollisionProp();
Vector vWorldMins, vWorldMaxs;
pCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
Vector ptCenter = pEntity->WorldSpaceCenter(); //(vWorldMins + vWorldMaxs) / 2.0f;
Vector ptOrigin = pEntity->GetAbsOrigin();
Vector vEntityCenterToOrigin = ptOrigin - ptCenter;
Vector ptPortalCenter = GetAbsOrigin();
Vector vPortalCenterToEntityCenter = ptCenter - ptPortalCenter;
Vector vPortalForward;
GetVectors( &vPortalForward, NULL, NULL );
Vector ptProjectedEntityCenter = ptPortalCenter + ( vPortalForward * vPortalCenterToEntityCenter.Dot( vPortalForward ) );
Vector ptDest;
if ( m_PortalSimulator.IsReadyToSimulate() )
{
Ray_t ray;
ray.Init( ptProjectedEntityCenter, ptCenter, vWorldMins - ptCenter, vWorldMaxs - ptCenter );
trace_t ShortestTrace;
ShortestTrace.fraction = 2.0f;
if( m_PortalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable )
{
physcollision->TraceBox( ray, m_PortalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable, vec3_origin, vec3_angle, &ShortestTrace );
}
/*if( pEnvironment->LocalCollide.pWorldCollide )
{
trace_t TempTrace;
physcollision->TraceBox( ray, pEnvironment->LocalCollide.pWorldCollide, vec3_origin, vec3_angle, &TempTrace );
if( TempTrace.fraction < ShortestTrace.fraction )
ShortestTrace = TempTrace;
}
if( pEnvironment->LocalCollide.pWallShellCollide )
{
trace_t TempTrace;
physcollision->TraceBox( ray, pEnvironment->LocalCollide.pWallShellCollide, vec3_origin, vec3_angle, &TempTrace );
if( TempTrace.fraction < ShortestTrace.fraction )
ShortestTrace = TempTrace;
}
if( pEnvironment->LocalCollide.pRemoteWorldWallCollide )
{
trace_t TempTrace;
physcollision->TraceBox( ray, pEnvironment->LocalCollide.pRemoteWorldWallCollide, vec3_origin, vec3_angle, &TempTrace );
if( TempTrace.fraction < ShortestTrace.fraction )
ShortestTrace = TempTrace;
}
//Add displacement checks here too?
*/
if( ShortestTrace.fraction < 2.0f )
{
Vector ptNewPos = ShortestTrace.endpos + vEntityCenterToOrigin;
pEntity->Teleport( &ptNewPos, NULL, NULL );
pEntity->IncrementInterpolationFrame();
#if !defined ( DISABLE_DEBUG_HISTORY )
if ( !IsMarkedForDeletion() )
{
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Teleporting %s inside 'ForceEntityToFitInPortalWall'\n", pEntity->GetDebugName() ) );
}
#endif
if( sv_portal_debug_touch.GetBool() )
{
DevMsg( "Teleporting %s inside 'ForceEntityToFitInPortalWall'\n", pEntity->GetDebugName() );
}
//pEntity->SetAbsOrigin( ShortestTrace.endpos + vEntityCenterToOrigin );
}
}
}
void CProp_Portal::UpdatePortalTeleportMatrix( void )
{
ResetModel();
//setup our origin plane
GetVectors( &m_plane_Origin.normal, NULL, NULL );
m_plane_Origin.dist = m_plane_Origin.normal.Dot( GetAbsOrigin() );
m_plane_Origin.signbits = SignbitsForPlane( &m_plane_Origin );
Vector vAbsNormal;
vAbsNormal.x = fabs(m_plane_Origin.normal.x);
vAbsNormal.y = fabs(m_plane_Origin.normal.y);
vAbsNormal.z = fabs(m_plane_Origin.normal.z);
if( vAbsNormal.x > vAbsNormal.y )
{
if( vAbsNormal.x > vAbsNormal.z )
{
if( vAbsNormal.x > 0.999f )
m_plane_Origin.type = PLANE_X;
else
m_plane_Origin.type = PLANE_ANYX;
}
else
{
if( vAbsNormal.z > 0.999f )
m_plane_Origin.type = PLANE_Z;
else
m_plane_Origin.type = PLANE_ANYZ;
}
}
else
{
if( vAbsNormal.y > vAbsNormal.z )
{
if( vAbsNormal.y > 0.999f )
m_plane_Origin.type = PLANE_Y;
else
m_plane_Origin.type = PLANE_ANYY;
}
else
{
if( vAbsNormal.z > 0.999f )
m_plane_Origin.type = PLANE_Z;
else
m_plane_Origin.type = PLANE_ANYZ;
}
}
if ( m_hLinkedPortal != NULL )
{
CProp_Portal_Shared::UpdatePortalTransformationMatrix( EntityToWorldTransform(), m_hLinkedPortal->EntityToWorldTransform(), &m_matrixThisToLinked );
m_hLinkedPortal->ResetModel();
//update the remote portal
MatrixInverseTR( m_matrixThisToLinked, m_hLinkedPortal->m_matrixThisToLinked );
}
else
{
m_matrixThisToLinked.Identity(); //don't accidentally teleport objects to zero space
}
}
void CProp_Portal::UpdatePortalLinkage( void )
{
if( m_bActivated )
{
CProp_Portal *pLink = m_hLinkedPortal.Get();
if( !(pLink && pLink->m_bActivated) )
{
//no old link, or inactive old link
if( pLink )
{
//we had an old link, must be inactive
if( pLink->m_hLinkedPortal.Get() != NULL )
pLink->UpdatePortalLinkage();
pLink = NULL;
}
int iPortalCount = s_PortalLinkageGroups[m_iLinkageGroupID].Count();
if( iPortalCount != 0 )
{
CProp_Portal **pPortals = s_PortalLinkageGroups[m_iLinkageGroupID].Base();
for( int i = 0; i != iPortalCount; ++i )
{
CProp_Portal *pCurrentPortal = pPortals[i];
if( pCurrentPortal == this )
continue;
if( pCurrentPortal->m_bActivated && pCurrentPortal->m_hLinkedPortal.Get() == NULL )
{
pLink = pCurrentPortal;
pCurrentPortal->m_hLinkedPortal = this;
pCurrentPortal->UpdatePortalLinkage();
break;
}
}
}
}
m_hLinkedPortal = pLink;
if( pLink != NULL )
{
CHandle<CProp_Portal> hThis = this;
CHandle<CProp_Portal> hRemote = pLink;
this->m_hLinkedPortal = hRemote;
pLink->m_hLinkedPortal = hThis;
m_bIsPortal2 = !m_hLinkedPortal->m_bIsPortal2;
// Initialize mics/speakers
if( m_hMicrophone == 0 )
{
inputdata_t inputdata;
m_hMicrophone = CreateEntityByName( "env_microphone" );
CEnvMicrophone *pMicrophone = static_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
pMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED );
pMicrophone->AddSpawnFlags( SF_MICROPHONE_SOUND_COMBAT | SF_MICROPHONE_SOUND_WORLD | SF_MICROPHONE_SOUND_PLAYER | SF_MICROPHONE_SOUND_BULLET_IMPACT | SF_MICROPHONE_SOUND_EXPLOSION );
DispatchSpawn( pMicrophone );
m_hSpeaker = CreateEntityByName( "env_speaker" );
CSpeaker *pSpeaker = static_cast<CSpeaker*>( m_hSpeaker.Get() );
if( !m_bIsPortal2 )
{
pSpeaker->SetName( MAKE_STRING( "PortalSpeaker1" ) );
pMicrophone->SetName( MAKE_STRING( "PortalMic1" ) );
pMicrophone->Activate();
pMicrophone->SetSpeakerName( MAKE_STRING( "PortalSpeaker2" ) );
pMicrophone->SetSensitivity( 10.0f );
}
else
{
pSpeaker->SetName( MAKE_STRING( "PortalSpeaker2" ) );
pMicrophone->SetName( MAKE_STRING( "PortalMic2" ) );
pMicrophone->Activate();
pMicrophone->SetSpeakerName( MAKE_STRING( "PortalSpeaker1" ) );
pMicrophone->SetSensitivity( 10.0f );
}
}
if ( m_hLinkedPortal->m_hMicrophone == 0 )
{
inputdata_t inputdata;
m_hLinkedPortal->m_hMicrophone = CreateEntityByName( "env_microphone" );
CEnvMicrophone *pLinkedMicrophone = static_cast<CEnvMicrophone*>( m_hLinkedPortal->m_hMicrophone.Get() );
pLinkedMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED );
pLinkedMicrophone->AddSpawnFlags( SF_MICROPHONE_SOUND_COMBAT | SF_MICROPHONE_SOUND_WORLD | SF_MICROPHONE_SOUND_PLAYER | SF_MICROPHONE_SOUND_BULLET_IMPACT | SF_MICROPHONE_SOUND_EXPLOSION );
DispatchSpawn( pLinkedMicrophone );
m_hLinkedPortal->m_hSpeaker = CreateEntityByName( "env_speaker" );
CSpeaker *pLinkedSpeaker = static_cast<CSpeaker*>( m_hLinkedPortal->m_hSpeaker.Get() );
if ( !m_bIsPortal2 )
{
pLinkedSpeaker->SetName( MAKE_STRING( "PortalSpeaker2" ) );
pLinkedMicrophone->SetName( MAKE_STRING( "PortalMic2" ) );
pLinkedMicrophone->Activate();
pLinkedMicrophone->SetSpeakerName( MAKE_STRING( "PortalSpeaker1" ) );
pLinkedMicrophone->SetSensitivity( 10.0f );
}
else
{
pLinkedSpeaker->SetName( MAKE_STRING( "PortalSpeaker1" ) );
pLinkedMicrophone->SetName( MAKE_STRING( "PortalMic1" ) );
pLinkedMicrophone->Activate();
pLinkedMicrophone->SetSpeakerName( MAKE_STRING( "PortalSpeaker2" ) );
pLinkedMicrophone->SetSensitivity( 10.0f );
}
}
// Set microphone/speaker positions
Vector vZero( 0.0f, 0.0f, 0.0f );
CEnvMicrophone *pMicrophone = static_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
pMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED );
pMicrophone->Teleport( &GetAbsOrigin(), &GetAbsAngles(), &vZero );
inputdata_t in;
pMicrophone->InputEnable( in );
CSpeaker *pSpeaker = static_cast<CSpeaker*>( m_hSpeaker.Get() );
pSpeaker->Teleport( &GetAbsOrigin(), &GetAbsAngles(), &vZero );
pSpeaker->InputTurnOn( in );
UpdatePortalTeleportMatrix();
}
else
{
m_PortalSimulator.DetachFromLinked();
m_PortalSimulator.ReleaseAllEntityOwnership();
}
Vector ptCenter = GetAbsOrigin();
QAngle qAngles = GetAbsAngles();
m_PortalSimulator.MoveTo( ptCenter, qAngles );
if( pLink )
m_PortalSimulator.AttachTo( &pLink->m_PortalSimulator );
if( m_pAttachedCloningArea )
m_pAttachedCloningArea->UpdatePosition();
}
else
{
CProp_Portal *pRemote = m_hLinkedPortal;
//apparently we've been deactivated
m_PortalSimulator.DetachFromLinked();
m_PortalSimulator.ReleaseAllEntityOwnership();
m_hLinkedPortal = NULL;
if( pRemote )
pRemote->UpdatePortalLinkage();
}
}
void CProp_Portal::PlacePortal( const Vector &vOrigin, const QAngle &qAngles, float fPlacementSuccess, bool bDelay /*= false*/ )
{
Vector vOldOrigin = GetLocalOrigin();
QAngle qOldAngles = GetLocalAngles();
Vector vNewOrigin = vOrigin;
QAngle qNewAngles = qAngles;
UTIL_TestForOrientationVolumes( qNewAngles, vNewOrigin, this );
if ( sv_portal_placement_never_fail.GetBool() )
{
fPlacementSuccess = PORTAL_FIZZLE_SUCCESS;
}
if ( fPlacementSuccess < 0.5f )
{
// Prepare fizzle
m_vDelayedPosition = vOrigin;
m_qDelayedAngles = qAngles;
if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_CANT_FIT )
m_iDelayedFailure = PORTAL_FIZZLE_CANT_FIT;
else if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_OVERLAP_LINKED )
m_iDelayedFailure = PORTAL_FIZZLE_OVERLAPPED_LINKED;
else if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_INVALID_VOLUME )
m_iDelayedFailure = PORTAL_FIZZLE_BAD_VOLUME;
else if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_INVALID_SURFACE )
m_iDelayedFailure = PORTAL_FIZZLE_BAD_SURFACE;
else if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_PASSTHROUGH_SURFACE )
m_iDelayedFailure = PORTAL_FIZZLE_NONE;
CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
if( pPortalGun )
{
CPortal_Player *pFiringPlayer = dynamic_cast<CPortal_Player *>( pPortalGun->GetOwner() );
if( pFiringPlayer )
{
g_PortalGameStats.Event_PortalPlacement( pFiringPlayer->GetAbsOrigin(), vOrigin, m_iDelayedFailure );
}
}
return;
}
if ( !bDelay )
{
m_vDelayedPosition = vNewOrigin;
m_qDelayedAngles = qNewAngles;
m_iDelayedFailure = PORTAL_FIZZLE_SUCCESS;
NewLocation( vNewOrigin, qNewAngles );
}
else
{
m_vDelayedPosition = vNewOrigin;
m_qDelayedAngles = qNewAngles;
m_iDelayedFailure = PORTAL_FIZZLE_SUCCESS;
}
CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
if( pPortalGun )
{
CPortal_Player *pFiringPlayer = dynamic_cast<CPortal_Player *>( pPortalGun->GetOwner() );
if( pFiringPlayer )
{
g_PortalGameStats.Event_PortalPlacement( pFiringPlayer->GetAbsOrigin(), vOrigin, m_iDelayedFailure );
}
}
}
void CProp_Portal::NewLocation( const Vector &vOrigin, const QAngle &qAngles )
{
// Tell our physics environment to stop simulating it's entities.
// Fast moving objects can pass through the hole this frame while it's in the old location.
m_PortalSimulator.ReleaseAllEntityOwnership();
Vector vOldForward;
GetVectors( &vOldForward, 0, 0 );
m_vPrevForward = vOldForward;
WakeNearbyEntities();
Teleport( &vOrigin, &qAngles, 0 );
if ( m_hMicrophone )
{
CEnvMicrophone *pMicrophone = static_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
pMicrophone->Teleport( &vOrigin, &qAngles, 0 );
inputdata_t in;
pMicrophone->InputEnable( in );
}
if ( m_hSpeaker )
{
CSpeaker *pSpeaker = static_cast<CSpeaker*>( m_hSpeaker.Get() );
pSpeaker->Teleport( &vOrigin, &qAngles, 0 );
inputdata_t in;
pSpeaker->InputTurnOn( in );
}
CreateSounds();
if ( m_pAmbientSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundChangeVolume( m_pAmbientSound, 0.4, 0.1 );
}
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_particles" ) : ( "portal_1_particles" ) ), PATTACH_POINT_FOLLOW, this, "particles_2", true );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_edge" ) : ( "portal_1_edge" ) ), PATTACH_POINT_FOLLOW, this, "particlespin" );
//if the other portal should be static, let's not punch stuff resting on it
bool bOtherShouldBeStatic = false;
if( !m_hLinkedPortal )
bOtherShouldBeStatic = true;
m_bActivated = true;
UpdatePortalLinkage();
UpdatePortalTeleportMatrix();
// Update the four corners of this portal for faster reference
UpdateCorners();
WakeNearbyEntities();
if ( m_hLinkedPortal )
{
m_hLinkedPortal->WakeNearbyEntities();
if( !bOtherShouldBeStatic )
{
m_hLinkedPortal->PunchAllPenetratingPlayers();
}
}
if ( m_bIsPortal2 )
{
EmitSound( "Portal.open_red" );
}
else
{
EmitSound( "Portal.open_blue" );
}
}
void CProp_Portal::InputSetActivatedState( inputdata_t &inputdata )
{
m_bActivated = inputdata.value.Bool();
m_hPlacedBy = NULL;
if ( m_bActivated )
{
Vector vOrigin;
vOrigin = GetAbsOrigin();
Vector vForward, vUp;
GetVectors( &vForward, 0, &vUp );
CTraceFilterSimpleClassnameList baseFilter( this, COLLISION_GROUP_NONE );
UTIL_Portal_Trace_Filter( &baseFilter );
CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
trace_t tr;
UTIL_TraceLine( vOrigin + vForward, vOrigin + vForward * -8.0f, MASK_SHOT_PORTAL, &traceFilterPortalShot, &tr );
QAngle qAngles;
VectorAngles( tr.plane.normal, vUp, qAngles );
float fPlacementSuccess = VerifyPortalPlacement( this, tr.endpos, qAngles, PORTAL_PLACED_BY_FIXED );
PlacePortal( tr.endpos, qAngles, fPlacementSuccess );
// If the fixed portal is overlapping a portal that was placed before it... kill it!
if ( fPlacementSuccess )
{
IsPortalOverlappingOtherPortals( this, vOrigin, GetAbsAngles(), true );
CreateSounds();
if ( m_pAmbientSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundChangeVolume( m_pAmbientSound, 0.4, 0.1 );
}
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_particles" ) : ( "portal_1_particles" ) ), PATTACH_POINT_FOLLOW, this, "particles_2", true );
DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_edge" ) : ( "portal_1_edge" ) ), PATTACH_POINT_FOLLOW, this, "particlespin" );
if ( m_bIsPortal2 )
{
EmitSound( "Portal.open_red" );
}
else
{
EmitSound( "Portal.open_blue" );
}
}
}
else
{
if ( m_pAmbientSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundChangeVolume( m_pAmbientSound, 0.0, 0.0 );
}
StopParticleEffects( this );
}
UpdatePortalTeleportMatrix();
UpdatePortalLinkage();
}
void CProp_Portal::InputFizzle( inputdata_t &inputdata )
{
DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
Fizzle();
}
//-----------------------------------------------------------------------------
// Purpose: Map can call new location, so far it's only for debugging purposes so it's not made to be very robust.
// Input : &inputdata - String with 6 float entries with space delimiters, location and orientation
//-----------------------------------------------------------------------------
void CProp_Portal::InputNewLocation( inputdata_t &inputdata )
{
char sLocationStats[MAX_PATH];
Q_strncpy( sLocationStats, inputdata.value.String(), sizeof(sLocationStats) );
// first 3 are location of new origin
Vector vNewOrigin;
char* pTok = strtok( sLocationStats, " " );
vNewOrigin.x = atof(pTok);
pTok = strtok( NULL, " " );
vNewOrigin.y = atof(pTok);
pTok = strtok( NULL, " " );
vNewOrigin.z = atof(pTok);
// Next 3 entries are new angles
QAngle vNewAngles;
pTok = strtok( NULL, " " );
vNewAngles.x = atof(pTok);
pTok = strtok( NULL, " " );
vNewAngles.y = atof(pTok);
pTok = strtok( NULL, " " );
vNewAngles.z = atof(pTok);
// Call main placement function (skipping placement rules)
NewLocation( vNewOrigin, vNewAngles );
}
void CProp_Portal::UpdateCorners()
{
Vector vOrigin = GetAbsOrigin();
Vector vUp, vRight;
GetVectors( NULL, &vRight, &vUp );
for ( int i = 0; i < 4; ++i )
{
Vector vAddPoint = vOrigin;
vAddPoint += vRight * ((i & (1<<0))?(PORTAL_HALF_WIDTH):(-PORTAL_HALF_WIDTH));
vAddPoint += vUp * ((i & (1<<1))?(PORTAL_HALF_HEIGHT):(-PORTAL_HALF_HEIGHT));
m_vPortalCorners[i] = vAddPoint;
}
}
void CProp_Portal::ChangeLinkageGroup( unsigned char iLinkageGroupID )
{
Assert( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) != -1 );
s_PortalLinkageGroups[m_iLinkageGroupID].FindAndRemove( this );
s_PortalLinkageGroups[iLinkageGroupID].AddToTail( this );
m_iLinkageGroupID = iLinkageGroupID;
}
CProp_Portal *CProp_Portal::FindPortal( unsigned char iLinkageGroupID, bool bPortal2, bool bCreateIfNothingFound /*= false*/ )
{
int iPortalCount = s_PortalLinkageGroups[iLinkageGroupID].Count();
if( iPortalCount != 0 )
{
CProp_Portal *pFoundInactive = NULL;
CProp_Portal **pPortals = s_PortalLinkageGroups[iLinkageGroupID].Base();
for( int i = 0; i != iPortalCount; ++i )
{
if( pPortals[i]->m_bIsPortal2 == bPortal2 )
{
if( pPortals[i]->m_bActivated )
return pPortals[i];
else
pFoundInactive = pPortals[i];
}
}
if( pFoundInactive )
return pFoundInactive;
}
if( bCreateIfNothingFound )
{
CProp_Portal *pPortal = (CProp_Portal *)CreateEntityByName( "prop_portal" );
pPortal->m_iLinkageGroupID = iLinkageGroupID;
pPortal->m_bIsPortal2 = bPortal2;
DispatchSpawn( pPortal );
return pPortal;
}
return NULL;
}
const CUtlVector<CProp_Portal *> *CProp_Portal::GetPortalLinkageGroup( unsigned char iLinkageGroupID )
{
return &s_PortalLinkageGroups[iLinkageGroupID];
}