You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2315 lines
73 KiB
2315 lines
73 KiB
//========= 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]; |
|
} |
|
|
|
|
|
|
|
|