//========= 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 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( m_hPlacedBy.Get() ); if( pPortalGun ) { CPortal_Player *pFiringPlayer = dynamic_cast( 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( 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(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( 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 hThis = this; CHandle 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( 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( 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( 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( 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( m_hMicrophone.Get() ); pMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED ); pMicrophone->Teleport( &GetAbsOrigin(), &GetAbsAngles(), &vZero ); inputdata_t in; pMicrophone->InputEnable( in ); CSpeaker *pSpeaker = static_cast( 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( m_hPlacedBy.Get() ); if( pPortalGun ) { CPortal_Player *pFiringPlayer = dynamic_cast( 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( m_hPlacedBy.Get() ); if( pPortalGun ) { CPortal_Player *pFiringPlayer = dynamic_cast( 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( m_hMicrophone.Get() ); pMicrophone->Teleport( &vOrigin, &qAngles, 0 ); inputdata_t in; pMicrophone->InputEnable( in ); } if ( m_hSpeaker ) { CSpeaker *pSpeaker = static_cast( 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::GetPortalLinkageGroup( unsigned char iLinkageGroupID ) { return &s_PortalLinkageGroups[iLinkageGroupID]; }