diff options
Diffstat (limited to 'game/server/portal/prop_portal.cpp')
| -rw-r--r-- | game/server/portal/prop_portal.cpp | 2315 |
1 files changed, 2315 insertions, 0 deletions
diff --git a/game/server/portal/prop_portal.cpp b/game/server/portal/prop_portal.cpp new file mode 100644 index 0000000..aaa5780 --- /dev/null +++ b/game/server/portal/prop_portal.cpp @@ -0,0 +1,2315 @@ +//========= 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 ( ¶msTeleport ); + 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]; +} + + + |