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.
2088 lines
69 KiB
2088 lines
69 KiB
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//===========================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "portal_base2d.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 "portal_base2d_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"
|
|
#include "vphysics/player_controller.h"
|
|
#include "saverestore_utlvector.h"
|
|
#include "baseprojector.h"
|
|
#include "prop_weightedcube.h"
|
|
#include "tier0/stackstats.h"
|
|
|
|
#include "portal2/portal_grabcontroller_shared.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include "tier0/memdbgon.h"
|
|
|
|
extern Vector Portal_FindUsefulVelocity( CBaseEntity *pOther );
|
|
|
|
ConVar sv_portal_debug_touch("sv_portal_debug_touch", "0", FCVAR_REPLICATED );
|
|
ConVar sv_portal_new_velocity_check("sv_portal_new_velocity_check", "1", FCVAR_CHEAT );
|
|
ConVar sv_portal_enable_microphone("sv_portal_enable_microphone", "0", FCVAR_DEVELOPMENTONLY );
|
|
ConVar sv_portal_microphone_sensitivity ( "sv_portal_microphone_sensitivity", "1.0f" );
|
|
ConVar sv_portal_microphone_max_range ( "sv_portal_microphone_max_range", "256.0f" );
|
|
ConVar sv_portal_high_speed_physics_early_untouch( "sv_portal_high_speed_physics_early_untouch", "1" );
|
|
extern ConVar sv_allow_mobile_portal_teleportation;
|
|
|
|
const char *CPortal_Base2D::s_szTestRestingSurfaceThinkContext = "CPortal_Base2D::TestRestingSurfaceThink";
|
|
const char *CPortal_Base2D::s_szDeactivatePortalNowContext = "CPortal_Base2D::DeactivatePortalNow";
|
|
|
|
BEGIN_DATADESC( CPortal_Base2D )
|
|
//saving
|
|
DEFINE_FIELD( m_hLinkedPortal, FIELD_EHANDLE ),
|
|
DEFINE_FIELD( m_matrixThisToLinked, FIELD_VMATRIX ),
|
|
DEFINE_KEYFIELD( m_bActivated, FIELD_BOOLEAN, "Activated" ),
|
|
DEFINE_KEYFIELD( m_bOldActivatedState, FIELD_BOOLEAN, "OldActivated" ),
|
|
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_FIELD( m_bMicAndSpeakersLinkedToRemote, FIELD_BOOLEAN ),
|
|
|
|
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_vOldPosition, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_qOldAngles, FIELD_VECTOR ),
|
|
DEFINE_FIELD( m_hPlacedBy, FIELD_EHANDLE ),
|
|
|
|
DEFINE_KEYFIELD( m_fNetworkHalfWidth, FIELD_FLOAT, "HalfWidth" ),
|
|
DEFINE_KEYFIELD( m_fNetworkHalfHeight, FIELD_FLOAT, "HalfHeight" ),
|
|
|
|
DEFINE_FIELD( m_bIsMobile, FIELD_BOOLEAN ),
|
|
|
|
// 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 ),
|
|
|
|
DEFINE_UTLVECTOR( m_PortalEventListeners, FIELD_EHANDLE ),
|
|
|
|
// Function Pointers
|
|
DEFINE_THINKFUNC( TestRestingSurfaceThink ),
|
|
DEFINE_THINKFUNC( DeactivatePortalNow ),
|
|
|
|
DEFINE_OUTPUT( m_OnPlacedSuccessfully, "OnPlacedSuccessfully" ),
|
|
DEFINE_OUTPUT( m_OnEntityTeleportFromMe, "OnEntityTeleportFromMe" ),
|
|
DEFINE_OUTPUT( m_OnPlayerTeleportFromMe, "OnPlayerTeleportFromMe" ),
|
|
DEFINE_OUTPUT( m_OnEntityTeleportToMe, "OnEntityTeleportToMe" ),
|
|
DEFINE_OUTPUT( m_OnPlayerTeleportToMe, "OnPlayerTeleportToMe" ),
|
|
|
|
DEFINE_FIELD( m_vPortalSpawnLocation, FIELD_VECTOR ),
|
|
|
|
END_DATADESC()
|
|
|
|
extern void SendProxy_Origin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID );
|
|
extern void SendProxy_Angles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID );
|
|
|
|
IMPLEMENT_SERVERCLASS_ST( CPortal_Base2D, DT_Portal_Base2D )
|
|
//upgrade origin and angles to high precision to prevent prediction errors with projected walls.
|
|
SendPropExclude( "DT_BaseEntity", "m_vecOrigin" ),
|
|
SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
|
|
SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_NOSCALE, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
|
|
SendPropVector( SENDINFO(m_angRotation), -1, SPROP_NOSCALE, 0.0f, HIGH_DEFAULT, SendProxy_Angles ),
|
|
|
|
//if we're resting on another entity, we still need ultra-precise absolute coords. We should probably downgrade local origin/angles in favor of these
|
|
SendPropVector( SENDINFO(m_ptOrigin), -1, SPROP_NOSCALE, 0.0f, HIGH_DEFAULT ),
|
|
SendPropVector( SENDINFO(m_qAbsAngle), -1, SPROP_NOSCALE, 0.0f, HIGH_DEFAULT ),
|
|
|
|
SendPropEHandle( SENDINFO(m_hLinkedPortal) ),
|
|
SendPropBool( SENDINFO(m_bActivated) ),
|
|
SendPropBool( SENDINFO(m_bOldActivatedState) ),
|
|
SendPropBool( SENDINFO(m_bIsPortal2) ),
|
|
SendPropFloat( SENDINFO( m_fNetworkHalfWidth ), -1, SPROP_NOSCALE, 0.0f, HIGH_DEFAULT ),
|
|
SendPropFloat( SENDINFO( m_fNetworkHalfHeight ), -1, SPROP_NOSCALE, 0.0f, HIGH_DEFAULT ),
|
|
SendPropBool( SENDINFO( m_bIsMobile ) ),
|
|
|
|
SendPropDataTable( SENDINFO_DT( m_PortalSimulator ), &REFERENCE_SEND_TABLE( DT_PortalSimulator ) )
|
|
END_SEND_TABLE()
|
|
|
|
LINK_ENTITY_TO_CLASS( portal_base2D, CPortal_Base2D );
|
|
|
|
|
|
CON_COMMAND_F( portal_place, "Places a portal. Indicate the group #, then the portal #, then pos + angle", FCVAR_CHEAT )
|
|
{
|
|
if ( args.ArgC() != 9 )
|
|
{
|
|
ConMsg( "Usage: portal_place <group #> <portal #> <pos.x pos.y pos.z> <angle.x angle.y angle.z>\n" );
|
|
return;
|
|
}
|
|
|
|
int iLinkageGroupID = atoi( args[1] );
|
|
// clamp to either 0, 1 or 2
|
|
iLinkageGroupID = MIN( iLinkageGroupID, 2 );
|
|
iLinkageGroupID = MAX( iLinkageGroupID, 0 );
|
|
|
|
bool bPortal2 = atoi( args[2] ) != 0;
|
|
CProp_Portal *pPortal = CProp_Portal::FindPortal( iLinkageGroupID, bPortal2, true );
|
|
if ( !pPortal )
|
|
{
|
|
ConMsg( "Error finding portal!\n" );
|
|
return;
|
|
}
|
|
|
|
Vector vNewOrigin;
|
|
vNewOrigin.x = atof( args[3] );
|
|
vNewOrigin.y = atof( args[4] );
|
|
vNewOrigin.z = atof( args[5] );
|
|
|
|
// Next 3 entries are new angles
|
|
QAngle vNewAngles;
|
|
vNewAngles.x = atof( args[6] );
|
|
vNewAngles.y = atof( args[7] );
|
|
vNewAngles.z = atof( args[8] );
|
|
|
|
// Call main placement function (skipping placement rules)
|
|
pPortal->NewLocation( vNewOrigin, vNewAngles );
|
|
}
|
|
|
|
void PortalReportFunc( bool bOnlySpewIfPortalsChanged = false )
|
|
{
|
|
struct PortalReportItem_t
|
|
{
|
|
Vector vOrigin;
|
|
QAngle vAngles;
|
|
int nIndex;
|
|
int iLinkageGroupID;
|
|
};
|
|
struct PortalReport_t
|
|
{
|
|
int nPortals;
|
|
int nPlayers;
|
|
PortalReportItem_t portals[4];
|
|
PortalReportItem_t players[2];
|
|
};
|
|
static PortalReport_t oldReport = { 0, 0 };
|
|
|
|
PortalReport_t newReport;
|
|
memset( &newReport, 0, sizeof( PortalReport_t ) );
|
|
|
|
// Portal linkage groups are 0 in single-player or 1 & 2 for first and second player in co-op
|
|
for ( int iLinkageGroupID = 0; iLinkageGroupID < 3; ++iLinkageGroupID )
|
|
{
|
|
for ( int nPortal = 0; nPortal < 2; ++nPortal )
|
|
{
|
|
CProp_Portal *pPortal = CProp_Portal::FindPortal( iLinkageGroupID, (nPortal != 0), false );
|
|
if ( !pPortal )
|
|
continue;
|
|
|
|
PortalReportItem_t &item = newReport.portals[ newReport.nPortals++ ];
|
|
item.vOrigin = pPortal->m_ptOrigin;
|
|
item.vAngles = pPortal->m_qAbsAngle;
|
|
item.nIndex = nPortal;
|
|
item.iLinkageGroupID = iLinkageGroupID;
|
|
}
|
|
}
|
|
|
|
// Player indices are 1 or 2 (only 1 in single-player)
|
|
for ( int i = 1; i <= 2; ++i )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
|
|
if ( !pPlayer )
|
|
continue;
|
|
|
|
PortalReportItem_t &item = newReport.players[ newReport.nPlayers++ ];
|
|
item.vOrigin = pPlayer->GetAbsOrigin();
|
|
item.vAngles = pPlayer->GetAbsAngles();
|
|
item.nIndex = i;
|
|
}
|
|
|
|
|
|
if ( !bOnlySpewIfPortalsChanged || memcmp( &oldReport.portals, &newReport.portals, sizeof( newReport.portals ) ) )
|
|
{
|
|
for ( int i = 0; i < newReport.nPortals; i++ )
|
|
{
|
|
PortalReportItem_t &item = newReport.portals[ i ];
|
|
ConMsg( "portal_place %d %d %.3f %.3f %.3f %.3f %.3f %.3f; ",
|
|
item.iLinkageGroupID, item.nIndex,
|
|
item.vOrigin.x, item.vOrigin.y, item.vOrigin.z,
|
|
item.vAngles.x, item.vAngles.y, item.vAngles.z );
|
|
}
|
|
for ( int i = 0; i < newReport.nPlayers; i++ )
|
|
{
|
|
PortalReportItem_t &item = newReport.players[ i ];
|
|
ConMsg( "cmd%d setpos_exact %.3f %.3f %.3f; cmd%d setang_exact %.3f %.3f %.3f; ",
|
|
item.nIndex, item.vOrigin.x, item.vOrigin.y, item.vOrigin.z,
|
|
item.nIndex, item.vAngles.x, item.vAngles.y, item.vAngles.z );
|
|
}
|
|
ConMsg( "\n" );
|
|
}
|
|
memcpy( &oldReport, &newReport, sizeof( PortalReport_t ) );
|
|
}
|
|
CON_COMMAND_F( portal_report, "Reports the location of all portals", FCVAR_CHEAT )
|
|
{
|
|
PortalReportFunc();
|
|
}
|
|
|
|
|
|
CPortal_Base2D::CPortal_Base2D( 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);
|
|
}
|
|
|
|
CPortal_Base2D_Shared::AllPortals.AddToTail( this );
|
|
|
|
// Make sure all listeners are clear
|
|
m_PortalEventListeners.Purge();
|
|
|
|
m_vPortalSpawnLocation.Invalidate();
|
|
}
|
|
|
|
CPortal_Base2D::~CPortal_Base2D( void )
|
|
{
|
|
CPortal_Base2D_Shared::AllPortals.FindAndRemove( this );
|
|
|
|
if( m_pCollisionShape )
|
|
{
|
|
physcollision->DestroyCollide( m_pCollisionShape );
|
|
m_pCollisionShape = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
void CPortal_Base2D::UpdateOnRemove( void )
|
|
{
|
|
m_PortalSimulator.ClearEverything();
|
|
|
|
RemovePortalMicAndSpeaker();
|
|
|
|
CPortal_Base2D *pRemote = m_hLinkedPortal;
|
|
if( pRemote != NULL )
|
|
{
|
|
m_PortalSimulator.DetachFromLinked();
|
|
m_hLinkedPortal = NULL;
|
|
SetActive( false );
|
|
m_bOldActivatedState = false;
|
|
pRemote->UpdatePortalLinkage();
|
|
pRemote->UpdatePortalTeleportMatrix();
|
|
}
|
|
|
|
if( m_pAttachedCloningArea )
|
|
{
|
|
UTIL_Remove( m_pAttachedCloningArea );
|
|
m_pAttachedCloningArea = NULL;
|
|
}
|
|
|
|
m_PortalEventListeners.Purge();
|
|
|
|
BaseClass::UpdateOnRemove();
|
|
}
|
|
|
|
void CPortal_Base2D::Spawn( void )
|
|
{
|
|
Precache();
|
|
|
|
UpdateCollisionShape();
|
|
|
|
Assert( (m_fNetworkHalfHeight > 0.0f) && (m_fNetworkHalfWidth > 0.0f) );
|
|
|
|
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 );
|
|
|
|
SetSize( GetLocalMins(), GetLocalMaxs() );
|
|
|
|
UpdateCorners();
|
|
|
|
if( sv_portal_enable_microphone.GetInt() )
|
|
{
|
|
CreateMicAndSpeaker();
|
|
}
|
|
|
|
BaseClass::Spawn();
|
|
|
|
m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
|
|
|
|
m_bMicAndSpeakersLinkedToRemote = false;
|
|
|
|
// Because we're not solid
|
|
AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID );
|
|
|
|
m_vPortalSpawnLocation = GetAbsOrigin();
|
|
}
|
|
|
|
void CPortal_Base2D::OnRestore()
|
|
{
|
|
m_ptOrigin = GetAbsOrigin();
|
|
m_qAbsAngle = GetAbsAngles();
|
|
|
|
UpdatePortalTeleportMatrix();
|
|
UpdateCorners();
|
|
Assert( m_pAttachedCloningArea == NULL );
|
|
m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
|
|
|
|
BaseClass::OnRestore();
|
|
}
|
|
|
|
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 CPortal_Base2D::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
|
|
{
|
|
if ( !m_pCollisionShape )
|
|
{
|
|
//HACK: This is a last-gasp type fix for a crash caused by m_pCollisionShape not yet being set up
|
|
// during a restore.
|
|
UpdateCollisionShape();
|
|
}
|
|
|
|
physcollision->TraceBox( ray, MASK_ALL, NULL, m_pCollisionShape, GetAbsOrigin(), GetAbsAngles(), &tr );
|
|
return tr.DidHit();
|
|
}
|
|
|
|
ConVar portal_test_resting_surface_for_paint( "portal_test_resting_surface_for_paint", "0", 0, "Test if a portal is on a white painted surface and fizzle if it goes away. Test it EVERY FRAME." );
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: When placed on a surface that could potentially go away (anything but world geo), we test for that condition and fizzle
|
|
//-----------------------------------------------------------------------------
|
|
void CPortal_Base2D::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;
|
|
|
|
bool bAnyCornerOnPortalPaint = false;
|
|
|
|
// Check corners
|
|
for ( int iCorner = 0; iCorner < 4; ++iCorner )
|
|
{
|
|
Vector vCorner = vOrigin;
|
|
|
|
if ( iCorner % 2 == 0 )
|
|
vCorner += vRight * ( m_fNetworkHalfWidth - PORTAL_BUMP_FORGIVENESS * 1.1f );
|
|
else
|
|
vCorner += -vRight * ( m_fNetworkHalfWidth - PORTAL_BUMP_FORGIVENESS * 1.1f );
|
|
|
|
if ( iCorner < 2 )
|
|
vCorner += vUp * ( m_fNetworkHalfHeight - PORTAL_BUMP_FORGIVENESS * 1.1f );
|
|
else
|
|
vCorner += -vUp * ( m_fNetworkHalfHeight - 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" );
|
|
DeactivatePortalOnThink();
|
|
SetContextThink( NULL, TICK_NEVER_THINK, s_szTestRestingSurfaceThinkContext );
|
|
break;
|
|
}
|
|
|
|
if ( portal_test_resting_surface_for_paint.GetBool() )
|
|
{
|
|
PortalSurfaceType_t portalSurfType = PortalSurfaceType( tr );
|
|
|
|
// check if we still on portal paint
|
|
if ( portalSurfType == PORTAL_SURFACE_PAINT )
|
|
{
|
|
bAnyCornerOnPortalPaint = true;
|
|
}
|
|
|
|
// This corner is on no portal surf without portal paint
|
|
if ( portalSurfType == PORTAL_SURFACE_INVALID )
|
|
{
|
|
DevMsg( "Portal paint removed from behind portal.\n" );
|
|
DeactivatePortalOnThink();
|
|
SetContextThink( NULL, TICK_NEVER_THINK, s_szTestRestingSurfaceThinkContext );
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( !tr.DidHitWorld() )
|
|
{
|
|
iCornersOnVolatileSurface++;
|
|
}
|
|
}
|
|
|
|
// Still on a movable or deletable surface or portal paint
|
|
if ( iCornersOnVolatileSurface > 0 || bAnyCornerOnPortalPaint )
|
|
{
|
|
SetContextThink ( &CPortal_Base2D::TestRestingSurfaceThink, gpGlobals->curtime + 0.1f, s_szTestRestingSurfaceThinkContext );
|
|
}
|
|
else
|
|
{
|
|
// All corners on world, we don't need to test
|
|
SetContextThink( NULL, TICK_NEVER_THINK, s_szTestRestingSurfaceThinkContext );
|
|
}
|
|
}
|
|
|
|
|
|
void CPortal_Base2D::DeactivatePortalOnThink( void )
|
|
{
|
|
if( IsActive() && (GetNextThink( s_szDeactivatePortalNowContext ) == TICK_NEVER_THINK) )
|
|
{
|
|
SetContextThink( &CPortal_Base2D::DeactivatePortalNow, gpGlobals->curtime, s_szDeactivatePortalNowContext );
|
|
}
|
|
}
|
|
|
|
|
|
void CPortal_Base2D::DeactivatePortalNow( void )
|
|
{
|
|
// if held entity is on the opposite side of portal, make the player drops the entity when either portal gets deactivated
|
|
for( int i = 1; i <= gpGlobals->maxClients; ++i )
|
|
{
|
|
CPortal_Player *pPlayer = ToPortalPlayer( UTIL_PlayerByIndex( i ) );
|
|
|
|
//If the other player exists and is connected
|
|
if( pPlayer && pPlayer->IsConnected() && pPlayer->IsUsingVMGrab() )
|
|
{
|
|
if ( GetPlayerHeldEntity( pPlayer ) && pPlayer->IsHeldObjectOnOppositeSideOfPortal() )
|
|
{
|
|
CPortal_Base2D *pHeldObjectPortal = pPlayer->GetHeldObjectPortal();
|
|
CPortal_Base2D *pPlayerPortal = pHeldObjectPortal->m_hLinkedPortal.Get();
|
|
if ( pPlayerPortal == this || pHeldObjectPortal == this )
|
|
{
|
|
pPlayer->ClearUseEntity();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CPortal_Base2D *pRemotePortal = m_hLinkedPortal;
|
|
|
|
StopParticleEffects( this );
|
|
|
|
SetActive( false );
|
|
m_bOldActivatedState = false;
|
|
m_hLinkedPortal = NULL;
|
|
m_PortalSimulator.DetachFromLinked();
|
|
m_PortalSimulator.ReleaseAllEntityOwnership();
|
|
|
|
if( pRemotePortal )
|
|
{
|
|
pRemotePortal->UpdatePortalLinkage();
|
|
}
|
|
|
|
UpdateClientCheckPVS();
|
|
|
|
SetMoveType( MOVETYPE_NONE );
|
|
SetMobileState( false );
|
|
|
|
SetContextThink( NULL, TICK_NEVER_THINK, s_szDeactivatePortalNowContext );
|
|
SetContextThink( NULL, TICK_NEVER_THINK, s_szTestRestingSurfaceThinkContext );
|
|
|
|
OnPortalDeactivated();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Removes the portal microphone and speakers. This is done in two places
|
|
// (fizzle and UpdateOnRemove) so the code is consolidated here.
|
|
// Input : -
|
|
//-----------------------------------------------------------------------------
|
|
void CPortal_Base2D::RemovePortalMicAndSpeaker()
|
|
{
|
|
|
|
// Shut down microphone/speaker if they exist
|
|
if ( m_hMicrophone )
|
|
{
|
|
CEnvMicrophone *pMicrophone = (CEnvMicrophone*)(m_hMicrophone.Get());
|
|
if ( pMicrophone )
|
|
{
|
|
inputdata_t inMicDisable;
|
|
pMicrophone->InputDisable( inMicDisable );
|
|
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() )
|
|
{
|
|
CPortal_Base2D* 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 inTurnOff;
|
|
pSpeaker->InputTurnOff( inTurnOff );
|
|
UTIL_Remove( pSpeaker );
|
|
}
|
|
m_hSpeaker = 0;
|
|
}
|
|
|
|
m_bMicAndSpeakersLinkedToRemote = false;
|
|
|
|
if ( m_hLinkedPortal.Get() )
|
|
{
|
|
if ( m_hLinkedPortal->m_hMicrophone.Get() )
|
|
{
|
|
CEnvMicrophone* pRemoteMic = dynamic_cast<CEnvMicrophone*>( m_hLinkedPortal->m_hMicrophone.Get() );
|
|
if ( pRemoteMic )
|
|
{
|
|
pRemoteMic->SetSpeaker( NULL_STRING, NULL );
|
|
}
|
|
}
|
|
m_hLinkedPortal->m_bMicAndSpeakersLinkedToRemote = false;
|
|
}
|
|
}
|
|
|
|
void CPortal_Base2D::PunchPenetratingPlayer( CBasePlayer *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 ) )
|
|
{
|
|
Ray_t playerRay;
|
|
playerRay.Init( pPlayer->GetAbsOrigin(), pPlayer->GetAbsOrigin(), pPlayer->GetPlayerMins(), pPlayer->GetPlayerMaxs() );
|
|
|
|
trace_t WorldTrace;
|
|
CTraceFilterSimple traceFilter( pPlayer, COLLISION_GROUP_PLAYER_MOVEMENT );
|
|
enginetrace->TraceRay( playerRay, MASK_PLAYERSOLID, &traceFilter, &WorldTrace );
|
|
|
|
if( WorldTrace.startsolid ) //player would be stuck unless moving using portal traces. Really good indicator that they're actually in the portal plane.
|
|
{
|
|
Vector vForward;
|
|
GetVectors( &vForward, 0, 0 );
|
|
vForward *= 100.0f;
|
|
pPlayer->VelocityPunch( vForward );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPortal_Base2D::PunchAllPenetratingPlayers( void )
|
|
{
|
|
for( int i = 1; i <= gpGlobals->maxClients; ++i )
|
|
{
|
|
CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
|
|
if ( pPlayer )
|
|
{
|
|
PunchPenetratingPlayer( pPlayer );
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPortal_Base2D::Activate( void )
|
|
{
|
|
UpdateCollisionShape();
|
|
|
|
if( m_pAttachedCloningArea == NULL )
|
|
m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
|
|
|
|
UpdatePortalTeleportMatrix();
|
|
|
|
UpdatePortalLinkage();
|
|
|
|
UpdateClientCheckPVS();
|
|
|
|
BaseClass::Activate();
|
|
|
|
AddEffects( EF_NOSHADOW | EF_NORECEIVESHADOW );
|
|
|
|
m_PortalSimulator.SetCarvedParent( GetParent() );
|
|
|
|
if( IsActive() && (m_hLinkedPortal.Get() != NULL) )
|
|
{
|
|
Vector ptCenter = GetAbsOrigin();
|
|
QAngle qAngles = GetAbsAngles();
|
|
SetSize( GetLocalMins(), GetLocalMaxs() );
|
|
m_PortalSimulator.SetSize( GetHalfWidth(), GetHalfHeight() );
|
|
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 )
|
|
{
|
|
if( (link->flags & FTOUCHLINK_START_TOUCH) == 0 )
|
|
continue; //not actually touching this thing
|
|
|
|
CBaseEntity *pOther = link->entityTouched;
|
|
bool bTeleportable = CPortal_Base2D_Shared::IsEntityTeleportable( pOther );
|
|
bool bShouldCloneAcrossPortals = CPortal_Base2D_Shared::ShouldPhysicsCloneNonTeleportableEntityAcrossPortals( pOther );
|
|
if( bTeleportable || bShouldCloneAcrossPortals )
|
|
{
|
|
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( bTeleportable )
|
|
{
|
|
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 );
|
|
}
|
|
}
|
|
else if( bShouldCloneAcrossPortals )
|
|
{
|
|
m_PortalSimulator.StartCloningEntityAcrossPortals( pOther );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPortal_Base2D::Touch( CBaseEntity *pOther )
|
|
{
|
|
if( pOther->IsPlayer() || (pOther == GetMoveParent()) )
|
|
return;
|
|
|
|
BaseClass::Touch( pOther );
|
|
pOther->Touch( this );
|
|
|
|
// Don't do anything on touch if it's not active
|
|
if( !IsActive() || (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 );
|
|
m_PortalSimulator.StopCloningEntityAcrossPortals( pOther );
|
|
return;
|
|
}
|
|
|
|
Assert( IsMobile() || ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) || (m_hLinkedPortal->IsMobile()) ||
|
|
(m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
|
|
|
|
if( IsMobile() || ((m_hLinkedPortal.Get() != NULL) && m_hLinkedPortal->IsMobile()) )
|
|
{
|
|
if( !sv_allow_mobile_portal_teleportation.GetBool() || !pOther->IsPlayer() )
|
|
return;
|
|
}
|
|
|
|
// 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 ) &&
|
|
((pOther->GetSolid() != SOLID_VPHYSICS) || !m_pCollisionShape || UTIL_IsCollideableIntersectingPhysCollide( pOther->GetCollideable(), m_pCollisionShape, m_ptOrigin, m_qAbsAngle )) )
|
|
{
|
|
DevMsg( "Moving brush intersected portal plane.\n" );
|
|
DeactivatePortalOnThink();
|
|
return;
|
|
}
|
|
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" );
|
|
DeactivatePortalOnThink();
|
|
return;
|
|
}
|
|
else if ( !sv_allow_mobile_portals.GetBool() && tr.m_pEnt && tr.m_pEnt->IsMoving() )
|
|
{
|
|
DevMsg( "Surface behind portal is moving.\n" );
|
|
DeactivatePortalOnThink();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( m_hLinkedPortal == NULL )
|
|
return;
|
|
|
|
if( sv_portal_high_speed_physics_early_untouch.GetBool() )
|
|
{
|
|
IPhysicsObject *pOtherPhysObject = pOther->VPhysicsGetObject();
|
|
if( (pOther->GetMoveType() == MOVETYPE_VPHYSICS) && (pOtherPhysObject != NULL) )
|
|
{
|
|
Vector vPhysVelocity; //physics velocity not necessarily equal to entity velocity
|
|
pOtherPhysObject->GetVelocity( &vPhysVelocity, NULL );
|
|
float fExitSpeed = m_plane_Origin.normal.Dot( vPhysVelocity );
|
|
|
|
if( fExitSpeed > 200.0f ) //200.0f is a magic number indicating "leaving the portal and unlikely to turn back in a single tick"
|
|
{
|
|
const CPhysCollide *pCollide = pOtherPhysObject->GetCollide();
|
|
if( pCollide )
|
|
{
|
|
//vphysics object leaving portal at a healthy velocity.
|
|
//If it's moving fast enough, it will have left our bounds by a large margin before we receive an EndTouch(). It's possible that "large margin" lets the object pass outside
|
|
// the area that we carved portal collision. See if we should remove it from the portal environment now instead so it always has valid collision
|
|
|
|
Vector vPos;
|
|
QAngle qAngle;
|
|
pOtherPhysObject->GetPosition( &vPos, &qAngle );
|
|
float fRadius = physcollision->CollideGetRadius( pCollide );
|
|
|
|
if( (m_plane_Origin.normal.Dot( vPos ) - fRadius) > m_plane_Origin.dist )
|
|
{
|
|
//has fully left portal hole, switching collision should be a safe operation
|
|
|
|
const float fCollisionDataEndsDist = m_PortalSimulator.GetInternalData().Placement.vCollisionCloneExtents.x; //we don't have collision data for the world past this plane!
|
|
float fDistToEndPlane = -((m_plane_Origin.normal.Dot( vPos ) - (m_plane_Origin.dist + fCollisionDataEndsDist)) + fRadius);
|
|
|
|
float fTimeToExit = fDistToEndPlane / fExitSpeed;
|
|
|
|
if( fTimeToExit < TICK_INTERVAL )
|
|
{
|
|
//at our current velocity, we will pass outside our carved collision within a tick, need to switch now.
|
|
|
|
#if 0 //debugging overlays
|
|
//projection of where we believe we absolutely must be colliding with the real world as a white OBB
|
|
NDebugOverlay::BoxAngles( vPos + vPhysVelocity * fTimeToExit, pOther->CollisionProp()->OBBMins(), pOther->CollisionProp()->OBBMaxs(), qAngle, 255, 255, 255, 64, 30.0f );
|
|
//where we are right now as a green OBB
|
|
NDebugOverlay::BoxAngles( vPos, pOther->CollisionProp()->OBBMins(), pOther->CollisionProp()->OBBMaxs(), qAngle, 0, 255, 0, 64, 30.0f );
|
|
#endif
|
|
|
|
m_PortalSimulator.ReleaseOwnershipOfEntity( pOther );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool bTeleportable = CPortal_Base2D_Shared::IsEntityTeleportable( pOther );
|
|
|
|
//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( !IsMobile() && !m_hLinkedPortal->IsMobile() )
|
|
{
|
|
bool bShouldCloneAcrossPortals = CPortal_Base2D_Shared::ShouldPhysicsCloneNonTeleportableEntityAcrossPortals( pOther );
|
|
if( bTeleportable || bShouldCloneAcrossPortals )
|
|
{
|
|
CCollisionProperty *pOtherCollision = pOther->CollisionProp();
|
|
Vector vWorldMins, vWorldMaxs;
|
|
pOtherCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
|
|
Vector ptOtherCenter = (vWorldMins + vWorldMaxs) / 2.0f;
|
|
|
|
//hmm, not in our environment, plane tests, sharing tests
|
|
if( (m_plane_Origin.normal.Dot( ptOtherCenter ) >= m_plane_Origin.dist) )
|
|
{
|
|
if( bTeleportable && !m_PortalSimulator.OwnsEntity( pOther ) )
|
|
{
|
|
if( SharedEnvironmentCheck( pOther ) )
|
|
{
|
|
CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
|
|
if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
|
|
pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
|
|
|
|
m_PortalSimulator.TakeOwnershipOfEntity( pOther );
|
|
}
|
|
}
|
|
else if( bShouldCloneAcrossPortals )
|
|
{
|
|
m_PortalSimulator.StartCloningEntityAcrossPortals( pOther );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if( bTeleportable && ShouldTeleportTouchingEntity( pOther ) &&
|
|
(m_PortalSimulator.OwnsEntity( pOther ) || IsMobile() || m_hLinkedPortal->IsMobile()) )
|
|
{
|
|
TeleportTouchingEntity( pOther );
|
|
}
|
|
}
|
|
|
|
void CPortal_Base2D::StartTouch( CBaseEntity *pOther )
|
|
{
|
|
if( pOther->IsPlayer() || (pOther == GetMoveParent()) )
|
|
return;
|
|
|
|
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() )
|
|
{
|
|
Vector vVelocity = Portal_FindUsefulVelocity( pOther );
|
|
DevMsg( "Portal %i StartTouch: %s : %f %f %f : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), vVelocity.x, vVelocity.y, vVelocity.z, 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) || (IsActive() == false) || IsMobile() || m_hLinkedPortal->IsMobile() )
|
|
return;
|
|
|
|
bool bTeleportable = CPortal_Base2D_Shared::IsEntityTeleportable( pOther );
|
|
bool bShouldCloneAcrossPortals = CPortal_Base2D_Shared::ShouldPhysicsCloneNonTeleportableEntityAcrossPortals( pOther );
|
|
if( bTeleportable || bShouldCloneAcrossPortals )
|
|
{
|
|
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( bTeleportable )
|
|
{
|
|
if( SharedEnvironmentCheck( pOther ) )
|
|
{
|
|
Assert( IsMobile() || m_hLinkedPortal->IsMobile() || ((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 );
|
|
}
|
|
}
|
|
else if( bShouldCloneAcrossPortals )
|
|
{
|
|
m_PortalSimulator.StartCloningEntityAcrossPortals( pOther );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void CPortal_Base2D::EndTouch( CBaseEntity *pOther )
|
|
{
|
|
if ( pOther->IsPlayer() || (pOther == GetMoveParent()) )
|
|
return;
|
|
|
|
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 ( !IsActive() || IsMobile() || ((m_hLinkedPortal.Get() != NULL) && m_hLinkedPortal->IsMobile()) )
|
|
{
|
|
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
|
|
IsCeilingPortal( -0.7071f ) && //most likely falling out of the portal
|
|
(m_PortalSimulator.GetInternalData().Placement.PortalPlane.m_Normal.Dot( pOther->WorldSpaceCenter() ) < m_PortalSimulator.GetInternalData().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
|
|
{
|
|
//only 1 of these 2 calls should actually perform work depending on how the entity interacts with portals
|
|
m_PortalSimulator.ReleaseOwnershipOfEntity( pOther );
|
|
m_PortalSimulator.StopCloningEntityAcrossPortals( pOther );
|
|
}
|
|
|
|
if ( sv_portal_debug_touch.GetBool() )
|
|
{
|
|
Vector vVelocity = Portal_FindUsefulVelocity( pOther );
|
|
DevMsg( "Portal %i EndTouch: %s : %f %f %f : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), vVelocity.x, vVelocity.y, vVelocity.z, 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
|
|
}
|
|
|
|
bool CPortal_Base2D::SharedEnvironmentCheck( CBaseEntity *pEntity )
|
|
{
|
|
Assert( IsMobile() || ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) || (m_hLinkedPortal->IsMobile()) ||
|
|
(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.GetInternalData().Placement.ptCenter).LengthSqr() < (ptCenter - pOwningSimulator->GetInternalData().Placement.ptCenter).LengthSqr() )
|
|
return true;
|
|
|
|
/*if( !m_hLinkedPortal->m_PortalSimulator.EntityIsInPortalHole( pEntity ) )
|
|
{
|
|
Vector vOtherVelocity;
|
|
pEntity->GetVelocity( &vOtherVelocity );
|
|
|
|
if( vOtherVelocity.Dot( m_PortalSimulator.GetInternalData().Placement.vForward ) < vOtherVelocity.Dot( m_hLinkedPortal->m_PortalSimulator.GetInternalData().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 CPortal_Base2D::WakeNearbyEntities( void )
|
|
{
|
|
CBaseEntity* pList[ 1024 ];
|
|
|
|
Vector vForward, vUp, vRight;
|
|
GetVectors( &vForward, &vRight, &vUp );
|
|
|
|
Vector ptOrigin = GetAbsOrigin();
|
|
QAngle qAngles = GetAbsAngles();
|
|
|
|
Vector vLocalMins = GetLocalMins();
|
|
Vector vLocalMaxs = GetLocalMaxs();
|
|
|
|
Vector ptOBBStart = ptOrigin;
|
|
ptOBBStart += vForward * vLocalMins.x;
|
|
ptOBBStart += vRight * vLocalMins.y;
|
|
ptOBBStart += vUp * vLocalMins.z;
|
|
|
|
|
|
vForward *= vLocalMaxs.x - vLocalMins.x;
|
|
vRight *= vLocalMaxs.y - vLocalMins.y;
|
|
vUp *= vLocalMaxs.z - 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, vLocalMins, vLocalMaxs,
|
|
ptEntityCenter, pEntCollision->GetCollisionAngles(), pEntCollision->OBBMins(), pEntCollision->OBBMaxs() ) )
|
|
{
|
|
pEntity->WakeRestingObjects();
|
|
//pEntity->SetGroundEntity( NULL );
|
|
|
|
if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
|
|
{
|
|
IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
|
|
|
|
//Check if the reflective cube is in its disabled state and enable it
|
|
if ( UTIL_IsReflectiveCube( pEntity ) || UTIL_IsSchrodinger( pEntity ) )
|
|
{
|
|
CPropWeightedCube *pReflectiveCube = assert_cast<CPropWeightedCube*>( pEntity );
|
|
pReflectiveCube->ExitDisabledState();
|
|
}
|
|
|
|
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 CPortal_Base2D::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;
|
|
|
|
const PS_SD_Static_Wall_Local_Brushes_t &WallBrushes = m_PortalSimulator.GetInternalData().Simulation.Static.Wall.Local.Brushes;
|
|
|
|
int iTestMask = MASK_SOLID;
|
|
if( pEntity->IsPlayer() )
|
|
{
|
|
iTestMask |= CONTENTS_PLAYERCLIP;
|
|
}
|
|
else if( pEntity->IsNPC() )
|
|
{
|
|
iTestMask |= CONTENTS_MONSTERCLIP;
|
|
}
|
|
|
|
for( int i = 0; i != ARRAYSIZE( WallBrushes.BrushSets ); ++i )
|
|
{
|
|
if( (WallBrushes.BrushSets[i].iSolidMask & iTestMask) != 0 )
|
|
{
|
|
trace_t TempTrace;
|
|
physcollision->TraceBox( ray, m_PortalSimulator.GetInternalData().Simulation.Static.Wall.Local.Brushes.BrushSets[i].pCollideable, vec3_origin, vec3_angle, &TempTrace );
|
|
if( TempTrace.fraction < ShortestTrace.fraction )
|
|
{
|
|
ShortestTrace = TempTrace;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( ShortestTrace.fraction < 2.0f )
|
|
{
|
|
Vector ptNewPos = ShortestTrace.endpos + vEntityCenterToOrigin;
|
|
pEntity->Teleport( &ptNewPos, NULL, NULL );
|
|
pEntity->AddEffects( EF_NOINTERP );
|
|
#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 CPortal_Base2D::UpdatePortalTeleportMatrix( void )
|
|
{
|
|
//copied from client to ensure the numbers match as closely as possible.
|
|
{
|
|
ALIGN16 matrix3x4_t finalMatrix;
|
|
if( GetMoveParent() )
|
|
{
|
|
// Construct the entity-to-world matrix
|
|
// Start with making an entity-to-parent matrix
|
|
ALIGN16 matrix3x4_t matEntityToParent;
|
|
AngleMatrix( GetLocalAngles(), matEntityToParent );
|
|
MatrixSetColumn( GetLocalOrigin(), 3, matEntityToParent );
|
|
|
|
// concatenate with our parent's transform
|
|
ALIGN16 matrix3x4_t scratchMatrix;
|
|
ConcatTransforms( GetParentToWorldTransform( scratchMatrix ), matEntityToParent, finalMatrix );
|
|
|
|
MatrixGetColumn( finalMatrix, 0, m_vForward );
|
|
MatrixGetColumn( finalMatrix, 1, m_vRight );
|
|
MatrixGetColumn( finalMatrix, 2, m_vUp );
|
|
Vector vTempOrigin;
|
|
MatrixGetColumn( finalMatrix, 3, vTempOrigin );
|
|
m_ptOrigin = vTempOrigin;
|
|
m_vRight = -m_vRight;
|
|
|
|
QAngle qTempAngle;
|
|
MatrixAngles( finalMatrix, qTempAngle );
|
|
m_qAbsAngle = qTempAngle;
|
|
}
|
|
else
|
|
{
|
|
AngleMatrix( m_qAbsAngle, finalMatrix );
|
|
MatrixGetColumn( finalMatrix, 0, m_vForward );
|
|
MatrixGetColumn( finalMatrix, 1, m_vRight );
|
|
MatrixGetColumn( finalMatrix, 2, m_vUp );
|
|
m_vRight = -m_vRight;
|
|
}
|
|
}
|
|
|
|
//setup our origin plane
|
|
m_plane_Origin.normal = m_vForward;
|
|
m_plane_Origin.dist = m_plane_Origin.normal.Dot( m_ptOrigin );
|
|
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;
|
|
}
|
|
}
|
|
|
|
UTIL_Portal_ComputeMatrix( this, m_hLinkedPortal.Get() );
|
|
}
|
|
|
|
|
|
void CPortal_Base2D::CreateMicAndSpeaker( void )
|
|
{
|
|
RemovePortalMicAndSpeaker();
|
|
|
|
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() );
|
|
|
|
float flMicrophoneSensitivity = sv_portal_microphone_sensitivity.GetFloat();
|
|
flMicrophoneSensitivity = clamp( flMicrophoneSensitivity, 0.0f, 10.0f );
|
|
|
|
float flMicrophoneRange = sv_portal_microphone_max_range.GetFloat();
|
|
if ( flMicrophoneRange < 0.0f )
|
|
{
|
|
flMicrophoneRange = 0.0f;
|
|
}
|
|
|
|
pSpeaker->SetName( MAKE_STRING( m_bIsPortal2 ? "PortalMicrophone_2" : "PortalMicrophone_1" ) );
|
|
pMicrophone->SetName( MAKE_STRING( m_bIsPortal2 ? "PortalSpeaker_2" : "PortalSpeaker_1" ) );
|
|
pMicrophone->Activate();
|
|
pMicrophone->SetSensitivity( flMicrophoneSensitivity );
|
|
pMicrophone->SetMaxRange( flMicrophoneRange );
|
|
|
|
// Set microphone/speaker positions
|
|
pMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED );
|
|
pMicrophone->Teleport( &GetAbsOrigin(), &GetAbsAngles(), &vec3_origin );
|
|
pSpeaker->Teleport( &GetAbsOrigin(), &GetAbsAngles(), &vec3_origin );
|
|
}
|
|
|
|
void CPortal_Base2D::UpdatePortalLinkage( void )
|
|
{
|
|
if( IsActive() )
|
|
{
|
|
CPortal_Base2D *pLink = m_hLinkedPortal.Get();
|
|
|
|
if( pLink != NULL )
|
|
{
|
|
CHandle<CPortal_Base2D> hThis = this;
|
|
CHandle<CPortal_Base2D> hRemote = pLink;
|
|
|
|
this->m_hLinkedPortal = hRemote;
|
|
pLink->m_hLinkedPortal = hThis;
|
|
m_bIsPortal2 = !m_hLinkedPortal->m_bIsPortal2;
|
|
|
|
// Link up mic and speakers to remote portal
|
|
// NOTE: This does the work for both portals
|
|
if( sv_portal_enable_microphone.GetInt() )
|
|
{
|
|
|
|
if ( m_bMicAndSpeakersLinkedToRemote == false )
|
|
{
|
|
// Initialize mics/speakers
|
|
if( m_hMicrophone.Get() == NULL || m_hSpeaker.Get() == NULL )
|
|
{
|
|
|
|
CreateMicAndSpeaker();
|
|
}
|
|
|
|
if ( m_hLinkedPortal->m_hMicrophone.Get() == NULL || m_hSpeaker.Get() == NULL )
|
|
{
|
|
m_hLinkedPortal->CreateMicAndSpeaker();
|
|
}
|
|
|
|
// Cross link the mics and speakers
|
|
CEnvMicrophone* pMyMic = dynamic_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
|
|
CEnvMicrophone* pRemoteMic = dynamic_cast<CEnvMicrophone*>( m_hLinkedPortal->m_hMicrophone.Get() );
|
|
|
|
Assert( pMyMic && pRemoteMic && m_hSpeaker.Get() && m_hLinkedPortal->m_hSpeaker.Get() );
|
|
if ( pMyMic )
|
|
{
|
|
pMyMic->SetSpeaker( m_hLinkedPortal->m_hSpeaker->GetEntityName(), m_hLinkedPortal->m_hSpeaker );
|
|
}
|
|
if ( pRemoteMic )
|
|
{
|
|
pRemoteMic->SetSpeaker( m_hSpeaker->GetEntityName(), m_hSpeaker );
|
|
}
|
|
|
|
m_bMicAndSpeakersLinkedToRemote = true;
|
|
m_hLinkedPortal->m_bMicAndSpeakersLinkedToRemote = true;
|
|
}
|
|
}
|
|
|
|
UpdatePortalTeleportMatrix();
|
|
}
|
|
else
|
|
{
|
|
m_PortalSimulator.DetachFromLinked();
|
|
m_PortalSimulator.ReleaseAllEntityOwnership();
|
|
|
|
if ( m_hMicrophone.Get() )
|
|
{
|
|
CEnvMicrophone* pMyMic = dynamic_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
|
|
if ( pMyMic )
|
|
{
|
|
pMyMic->SetSpeaker( NULL_STRING, NULL );
|
|
}
|
|
}
|
|
m_bMicAndSpeakersLinkedToRemote = false;
|
|
|
|
}
|
|
|
|
|
|
m_PortalSimulator.SetSize( GetHalfWidth(), GetHalfHeight() );
|
|
m_PortalSimulator.MoveTo( m_ptOrigin, m_qAbsAngle );
|
|
|
|
if( pLink )
|
|
{
|
|
m_PortalSimulator.AttachTo( &pLink->m_PortalSimulator );
|
|
if( IsMobile() || pLink->IsMobile() )
|
|
{
|
|
SetSize( GetLocalMins(), Vector( 4.0f, m_fNetworkHalfWidth, m_fNetworkHalfHeight ) );
|
|
pLink->SetSize( GetLocalMins(), Vector( 4.0f, m_fNetworkHalfWidth, m_fNetworkHalfHeight ) );
|
|
}
|
|
else
|
|
{
|
|
SetSize( GetLocalMins(), GetLocalMaxs() );
|
|
pLink->SetSize( GetLocalMins(), GetLocalMaxs() );
|
|
}
|
|
}
|
|
|
|
if( m_pAttachedCloningArea )
|
|
m_pAttachedCloningArea->UpdatePosition();
|
|
}
|
|
else
|
|
{
|
|
CPortal_Base2D *pRemote = m_hLinkedPortal;
|
|
//apparently we've been deactivated
|
|
m_PortalSimulator.DetachFromLinked();
|
|
m_PortalSimulator.ReleaseAllEntityOwnership();
|
|
|
|
m_hLinkedPortal = NULL;
|
|
if( pRemote )
|
|
{
|
|
pRemote->UpdatePortalLinkage();
|
|
}
|
|
}
|
|
|
|
if( m_bIsPortal2 )
|
|
{
|
|
m_PortalSimulator.EditDebuggingData().overlayColor.SetColor( 255, 0, 0, 255 );
|
|
}
|
|
else
|
|
{
|
|
m_PortalSimulator.EditDebuggingData().overlayColor.SetColor( 0, 0, 255, 255 );
|
|
}
|
|
|
|
// 79483: We have a handle on perf now and don't need to dump this every portal placement.
|
|
#if 0
|
|
#if !defined( _CERT )
|
|
// Make sure we have the last state of all portals in the console log, in case of a crash
|
|
PortalReportFunc( true );
|
|
#endif // !_CERT
|
|
#endif
|
|
}
|
|
|
|
|
|
//#define STACK_ANALYZE_PORTALPLACEMENT
|
|
|
|
#if defined( STACK_ANALYZE_PORTALPLACEMENT )
|
|
struct PortalPlacementInfo_t
|
|
{
|
|
DECLARE_CALLSTACKSTATSTRUCT();
|
|
DECLARE_CALLSTACKSTATSTRUCT_FIELDDESCRIPTION();
|
|
|
|
int iEntered;
|
|
int iActuallyMoved;
|
|
int iMaxTest;
|
|
int iMinTest;
|
|
};
|
|
|
|
BEGIN_STATSTRUCTDESCRIPTION( PortalPlacementInfo_t )
|
|
WRITE_STATSTRUCT_FIELDDESCRIPTION();
|
|
//WRITE_STATSTRUCT_FIELDMERGESCRIPT( SSMSL_Squirrel );
|
|
END_STATSTRUCTDESCRIPTION()
|
|
|
|
BEGIN_STATSTRUCTFIELDDESCRIPTION( PortalPlacementInfo_t )
|
|
DEFINE_STATSTRUCTFIELD( iEntered, BasicStatStructFieldDesc, ( BSSFT_INT32, BSSFCM_ADD ) )
|
|
DEFINE_STATSTRUCTFIELD( iActuallyMoved, BasicStatStructFieldDesc, ( BSSFT_INT32, BSSFCM_ADD ) )
|
|
DEFINE_STATSTRUCTFIELD( iMaxTest, BasicStatStructFieldDesc, ( BSSFT_INT32, BSSFCM_MAX ) )
|
|
DEFINE_STATSTRUCTFIELD( iMinTest, BasicStatStructFieldDesc, ( BSSFT_INT32, BSSFCM_MIN ) )
|
|
END_STATSTRUCTFIELDDESCRIPTION()
|
|
|
|
CCallStackStatsGatherer<PortalPlacementInfo_t, 32, GetCallStack_Fast> s_PortalPlacementStats;
|
|
CCallStackStatsGatherer_Standardized_t g_PlacementStats = s_PortalPlacementStats;
|
|
|
|
void DumpPlacementStats()
|
|
{
|
|
s_PortalPlacementStats.DumpToFile( "PlacementStats.vcsf" );
|
|
}
|
|
|
|
static ConCommand dump_placement( "dump_placement", DumpPlacementStats, "", FCVAR_CHEAT );
|
|
#endif
|
|
|
|
|
|
|
|
void CPortal_Base2D::NewLocation( const Vector &vOrigin, const QAngle &qAngles )
|
|
{
|
|
#if defined( STACK_ANALYZE_PORTALPLACEMENT )
|
|
CCallStackStatsGatherer_StructAccessor_AutoLock<PortalPlacementInfo_t> entry = s_PortalPlacementStats.GetEntry();
|
|
++entry->iEntered;
|
|
entry->iMaxTest = RandomInt(0, 10000);
|
|
entry->iMinTest = RandomInt(0, 10000);
|
|
if( vOrigin != m_ptOrigin || qAngles != m_qAbsAngle )
|
|
{
|
|
++entry->iActuallyMoved;
|
|
}
|
|
#endif
|
|
|
|
// 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;
|
|
|
|
SetParent( NULL );
|
|
SetAbsVelocity( vec3_origin );
|
|
WakeNearbyEntities();
|
|
|
|
SetMobileState( false );
|
|
Teleport( &vOrigin, &qAngles, 0 );
|
|
|
|
m_ptOrigin = vOrigin;
|
|
m_qAbsAngle = qAngles;
|
|
|
|
if ( m_hMicrophone )
|
|
{
|
|
CEnvMicrophone *pMicrophone = static_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
|
|
pMicrophone->Teleport( &vOrigin, &qAngles, 0 );
|
|
inputdata_t inMicEnable;
|
|
pMicrophone->InputEnable( inMicEnable );
|
|
}
|
|
|
|
if ( m_hSpeaker )
|
|
{
|
|
CSpeaker *pSpeaker = static_cast<CSpeaker*>( m_hSpeaker.Get() );
|
|
pSpeaker->Teleport( &vOrigin, &qAngles, 0 );
|
|
inputdata_t inTurnOn;
|
|
pSpeaker->InputTurnOn( inTurnOn );
|
|
}
|
|
|
|
//if the other portal should be static, let's not punch stuff resting on it
|
|
bool bOtherShouldBeStatic = false;
|
|
if( !m_hLinkedPortal )
|
|
bOtherShouldBeStatic = true;
|
|
|
|
if ( IsActive() )
|
|
{
|
|
BroadcastPortalEvent( PORTALEVENT_MOVED );
|
|
}
|
|
|
|
SetActive( true );
|
|
|
|
UpdatePortalLinkage();
|
|
UpdatePortalTeleportMatrix();
|
|
|
|
// Update the four corners of this portal for faster reference
|
|
UpdateCorners();
|
|
|
|
WakeNearbyEntities();
|
|
|
|
// Make sure it's not a floor portal... allowing those to punch creates a floor to floor exploit
|
|
if ( !IsFloorPortal() )
|
|
{
|
|
if ( m_hLinkedPortal )
|
|
{
|
|
m_hLinkedPortal->WakeNearbyEntities();
|
|
if ( !bOtherShouldBeStatic )
|
|
{
|
|
m_hLinkedPortal->PunchAllPenetratingPlayers();
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdateClientCheckPVS();
|
|
|
|
CBaseEntity *pAttachedToMovingEntity;
|
|
|
|
//check to see if we landed on an entity that could potentially move
|
|
{
|
|
trace_t tr;
|
|
Vector vForward, vUp;
|
|
AngleVectors( qAngles, &vForward, NULL, &vUp );
|
|
UTIL_TraceLine( vOrigin + vForward * 5.0f, vOrigin - vForward * 10.0f, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
|
|
|
|
if( tr.m_pEnt && ((tr.m_pEnt->GetMoveType() != MOVETYPE_NONE) || (tr.m_pEnt->GetParent() != NULL)) )
|
|
{
|
|
pAttachedToMovingEntity = tr.m_pEnt;
|
|
|
|
QAngle qNewAngles; //recompute the angles since they may have changed between when we fired and when we landed
|
|
VectorAngles( vForward, vUp, qNewAngles );
|
|
SetAbsAngles( qNewAngles );
|
|
SetAbsOrigin( tr.endpos );
|
|
m_ptOrigin = tr.endpos;
|
|
m_qAbsAngle = qNewAngles;
|
|
}
|
|
else
|
|
{
|
|
pAttachedToMovingEntity = NULL;
|
|
}
|
|
}
|
|
|
|
if( pAttachedToMovingEntity )
|
|
{
|
|
SetMoveType( MOVETYPE_NOCLIP ); //switch the movetype to something that gets CPortal_Base2D::PhysicsSimulate() called.
|
|
SetParent( pAttachedToMovingEntity );
|
|
}
|
|
else
|
|
{
|
|
SetMoveType( MOVETYPE_NONE );
|
|
}
|
|
|
|
m_PortalSimulator.SetCarvedParent( pAttachedToMovingEntity );
|
|
|
|
SetMobileState( sv_allow_mobile_portals.GetBool() && (pAttachedToMovingEntity != NULL) && UTIL_IsEntityMovingOrRotating( pAttachedToMovingEntity ) );
|
|
|
|
m_vPortalSpawnLocation = m_ptOrigin;
|
|
PhysicsTouchTriggers( NULL );
|
|
}
|
|
|
|
void CPortal_Base2D::PhysicsSimulate( void )
|
|
{
|
|
BaseClass::PhysicsSimulate();
|
|
|
|
//update as if placed in a new position if we're mobile
|
|
bool bMoving = GetParent() && UTIL_IsEntityMovingOrRotating( GetParent() );
|
|
bool bPortalMoving = m_vPortalSpawnLocation.IsValid() && m_vPortalSpawnLocation.DistToSqr( GetAbsOrigin() ) > 0.1f;
|
|
|
|
if( ( bMoving || bPortalMoving ) && !sv_allow_mobile_portals.GetBool() )
|
|
{
|
|
DeactivatePortalOnThink();
|
|
SetMoveType( MOVETYPE_NONE );
|
|
SetParent( NULL );
|
|
return;
|
|
}
|
|
|
|
SetMobileState( bMoving );
|
|
|
|
if( bMoving )
|
|
{
|
|
UpdatePortalLinkage(); //this needs to change names or something. It does so much more than just linkage
|
|
UpdatePortalTeleportMatrix();
|
|
m_PortalSimulator.MoveTo( m_ptOrigin, m_qAbsAngle );
|
|
UpdateCorners();
|
|
|
|
#if 0 //mobile portal debugging code
|
|
NDebugOverlay::EntityBounds( this, 0, 255, 0, 50, 10.0f );
|
|
|
|
CPortal_Base2D *pLinked = m_hLinkedPortal;
|
|
if( m_hLinkedPortal )
|
|
{
|
|
Vector vTransformedOrigin = pLinked->m_PortalSimulator.m_DataAccess.Placement.matThisToLinked * pLinked->m_PortalSimulator.m_DataAccess.Placement.ptCenter;
|
|
Vector vTransformedForward = pLinked->m_PortalSimulator.m_DataAccess.Placement.matThisToLinked.ApplyRotation( pLinked->m_PortalSimulator.m_DataAccess.Placement.vForward );
|
|
|
|
NDebugOverlay::Line( vTransformedOrigin, vTransformedOrigin + vTransformedForward * (-50.0f), 100, 0, 0, true, 10.0f );
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
void CPortal_Base2D::SetMobileState( bool bSet )
|
|
{
|
|
if( m_bIsMobile != bSet )
|
|
{
|
|
m_bIsMobile = bSet;
|
|
if( bSet )
|
|
{
|
|
//disable physics if it's setup
|
|
//m_PortalSimulator.DetachFromLinked();
|
|
m_PortalSimulator.SetCollisionGenerationEnabled( false );
|
|
m_PortalSimulator.ReleaseAllEntityOwnership();
|
|
if( m_hLinkedPortal.Get() != NULL )
|
|
{
|
|
m_hLinkedPortal->m_PortalSimulator.ReleaseAllEntityOwnership();
|
|
}
|
|
|
|
if( IsActive() && (GetNextThink( s_szTestRestingSurfaceThinkContext ) == TICK_NEVER_THINK) )
|
|
SetContextThink( &CPortal_Base2D::TestRestingSurfaceThink, gpGlobals->curtime + 0.1f, s_szTestRestingSurfaceThinkContext );
|
|
}
|
|
else
|
|
{
|
|
//re-enable physics if we're supposed to have it working
|
|
m_PortalSimulator.SetCollisionGenerationEnabled( true );
|
|
|
|
if( IsActive() )
|
|
{
|
|
UpdatePortalTeleportMatrix();
|
|
m_PortalSimulator.MoveTo( m_ptOrigin, m_qAbsAngle );
|
|
m_PortalSimulator.SetSize( GetHalfWidth(), GetHalfHeight() );
|
|
|
|
CPortal_Base2D *pLink = m_hLinkedPortal.Get();
|
|
if( pLink && !pLink->m_bIsMobile )
|
|
m_PortalSimulator.AttachTo( &pLink->m_PortalSimulator );
|
|
}
|
|
}
|
|
|
|
bool bSmallSize = bSet;
|
|
|
|
CPortal_Base2D *pLinked = m_hLinkedPortal;
|
|
if( pLinked )
|
|
{
|
|
bSmallSize |= pLinked->IsMobile();
|
|
|
|
pLinked->SetSize( pLinked->GetLocalMins(), bSmallSize ? Vector( 4.0f, pLinked->m_fNetworkHalfWidth, pLinked->m_fNetworkHalfHeight ) : pLinked->GetLocalMaxs() );
|
|
}
|
|
|
|
SetSize( GetLocalMins(), bSmallSize ? Vector( 4.0f, m_fNetworkHalfWidth, m_fNetworkHalfHeight ) : GetLocalMaxs() );
|
|
}
|
|
}
|
|
|
|
|
|
void CPortal_Base2D::Resize( float fHalfWidth, float fHalfHeight )
|
|
{
|
|
if( (fHalfWidth == m_fNetworkHalfWidth) && (fHalfHeight == m_fNetworkHalfHeight) )
|
|
return;
|
|
|
|
m_fNetworkHalfWidth = fHalfWidth;
|
|
m_fNetworkHalfHeight = fHalfHeight;
|
|
|
|
CPortal_Base2D *pLinked = m_hLinkedPortal;
|
|
if( pLinked )
|
|
{
|
|
if( (m_fNetworkHalfWidth != pLinked->m_fNetworkHalfWidth) || (m_fNetworkHalfHeight != pLinked->m_fNetworkHalfHeight) )
|
|
{
|
|
//different portal sizes, unsupported, unlink. Scaling is a whole different ball of wax.
|
|
//if you're resizing both portals. They'll find eachother in UpdatePortalLinkage() once they're both resized.
|
|
m_hLinkedPortal = NULL;
|
|
m_PortalSimulator.DetachFromLinked();
|
|
pLinked->m_hLinkedPortal = NULL;
|
|
pLinked->m_PortalSimulator.DetachFromLinked();
|
|
}
|
|
}
|
|
|
|
UpdateCollisionShape();
|
|
if( m_pAttachedCloningArea )
|
|
m_pAttachedCloningArea->Resize( fHalfWidth, fHalfHeight );
|
|
|
|
m_PortalSimulator.SetSize( fHalfWidth, fHalfHeight );
|
|
|
|
if( pLinked )
|
|
pLinked->UpdatePortalLinkage();
|
|
|
|
UpdatePortalLinkage();
|
|
|
|
CBaseProjector::TestAllForProjectionChanges();
|
|
}
|
|
|
|
|
|
void CPortal_Base2D::UpdateClientCheckPVS( void )
|
|
{
|
|
|
|
for ( int i = 1; i <= gpGlobals->maxClients; ++i )
|
|
{
|
|
CPortal_Player *pPlayer = dynamic_cast<CPortal_Player*>(UTIL_PlayerByIndex( i ));
|
|
if ( pPlayer == NULL )
|
|
continue;
|
|
|
|
pPlayer->MarkClientCheckPVSDirty();
|
|
}
|
|
}
|
|
|
|
void CPortal_Base2D::UpdateCorners()
|
|
{
|
|
Vector vOrigin = m_ptOrigin;
|
|
Vector vUp, vRight;
|
|
GetVectors( NULL, &vRight, &vUp );
|
|
vRight *= m_fNetworkHalfWidth;
|
|
vUp *= m_fNetworkHalfHeight;
|
|
|
|
m_vPortalCorners[0] = (vOrigin + vRight) + vUp;
|
|
m_vPortalCorners[1] = (vOrigin - vRight) + vUp;
|
|
m_vPortalCorners[2] = (vOrigin - vRight) - vUp;
|
|
m_vPortalCorners[3] = (vOrigin + vRight) - vUp;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Tell all listeners about an event that just occurred
|
|
//-----------------------------------------------------------------------------
|
|
void CPortal_Base2D::OnPortalDeactivated( void )
|
|
{
|
|
BroadcastPortalEvent( PORTALEVENT_FIZZLE );
|
|
CBaseProjector::TestAllForProjectionChanges();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Tell all listeners about an event that just occurred
|
|
//-----------------------------------------------------------------------------
|
|
void CPortal_Base2D::BroadcastPortalEvent( PortalEvent_t nEventType )
|
|
{
|
|
/*
|
|
switch( nEventType )
|
|
{
|
|
case PORTALEVENT_MOVED:
|
|
Msg("[ Portal moved ]\n");
|
|
break;
|
|
|
|
case PORTALEVENT_FIZZLE:
|
|
Msg("[ Portal fizzled ]\n");
|
|
break;
|
|
|
|
case PORTALEVENT_LINKED:
|
|
Msg("[ Portal linked ]\n");
|
|
break;
|
|
}
|
|
*/
|
|
|
|
// We need to walk the list backwards because callers can remove themselves from our list as they're notified
|
|
for ( int i = m_PortalEventListeners.Count()-1; i >= 0; i-- )
|
|
{
|
|
if ( m_PortalEventListeners[i] == NULL )
|
|
continue;
|
|
|
|
m_PortalEventListeners[i]->NotifyPortalEvent( nEventType, this );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Add a listener to our collection
|
|
//-----------------------------------------------------------------------------
|
|
void CPortal_Base2D::AddPortalEventListener( EHANDLE hListener )
|
|
{
|
|
// Don't multiply add
|
|
if ( m_PortalEventListeners.Find( hListener ) != m_PortalEventListeners.InvalidIndex() )
|
|
return;
|
|
|
|
m_PortalEventListeners.AddToTail( hListener );
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Remove a listener to our collection
|
|
//-----------------------------------------------------------------------------
|
|
void CPortal_Base2D::RemovePortalEventListener( EHANDLE hListener )
|
|
{
|
|
m_PortalEventListeners.FindAndFastRemove( hListener );
|
|
}
|
|
|
|
// Adds the PVS of the cluster where the portal's partner is placed to the parameter PVS.
|
|
// NOTE: adds the *LINKED* portal's cluster, not the parameter portal.
|
|
void AddPortalVisibilityToPVS( CPortal_Base2D* pPortal, int outputpvslength, unsigned char *outputpvs )
|
|
{
|
|
Assert( pPortal );
|
|
if ( pPortal && pPortal->IsActivedAndLinked() )
|
|
{
|
|
CPortal_Base2D* pLinked = pPortal->m_hLinkedPortal.Get();
|
|
int iCluster = engine->GetClusterForOrigin( pLinked->GetAbsOrigin() );
|
|
|
|
// get the pvs for the linked portal's cluster
|
|
byte pvs[MAX_MAP_LEAFS/8];
|
|
engine->GetPVSForCluster( iCluster, sizeof( pvs ), pvs );
|
|
|
|
// Do the bulk on blocks of 4
|
|
uint32 nDWords = outputpvslength / 4;
|
|
uint32 *pInputDWords = (uint32*)pvs;
|
|
uint32 *RESTRICT pOutputDWords = (uint32*)outputpvs;
|
|
for ( int i=0; i<nDWords; ++i )
|
|
{
|
|
pOutputDWords[ i ] |= pInputDWords[ i ];
|
|
}
|
|
|
|
// Do the remaining (up to 3) in bytes
|
|
for ( int i=nDWords * 4; i<outputpvslength; ++i )
|
|
{
|
|
outputpvs[ i ] |= pvs[ i ];
|
|
}
|
|
}
|
|
}
|
|
|
|
CServerNetworkProperty *CPortal_Base2D::GetExtenderNetworkProp( void )
|
|
{
|
|
return NetworkProp();
|
|
}
|
|
|
|
const edict_t *CPortal_Base2D::GetExtenderEdict( void ) const
|
|
{
|
|
return edict();
|
|
}
|
|
|
|
Vector CPortal_Base2D::GetExtensionPVSOrigin( void )
|
|
{
|
|
return GetAbsOrigin() + m_plane_Origin.normal;
|
|
}
|
|
|
|
bool CPortal_Base2D::IsExtenderValid( void )
|
|
{
|
|
return IsActivedAndLinked();
|
|
}
|
|
|
|
int CPortal_Base2D::GetPolyVertCount( void )
|
|
{
|
|
return 4;
|
|
}
|
|
|
|
int CPortal_Base2D::ComputeFrustumThroughPolygon( const Vector &vVisOrigin, const VPlane *pInputFrustum, int iInputFrustumPlanes, VPlane *pOutputFrustum, int iOutputFrustumMaxPlanes )
|
|
{
|
|
int iReturnedPlanes = UTIL_CalcFrustumThroughConvexPolygon( m_vPortalCorners, 4, vVisOrigin, pInputFrustum, iInputFrustumPlanes, pOutputFrustum, iOutputFrustumMaxPlanes, 0 );
|
|
|
|
if( (iReturnedPlanes < iOutputFrustumMaxPlanes) && (iReturnedPlanes != 0) )
|
|
{
|
|
//add the portal plane as a near plane
|
|
pOutputFrustum[iReturnedPlanes].Init( -m_plane_Origin.normal, -m_plane_Origin.dist );
|
|
++iReturnedPlanes;
|
|
}
|
|
|
|
return iReturnedPlanes;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// AddPortalCornersToEnginePVS
|
|
// Subroutine to wrap the adding of portal corners to the PVS which is called once for the setup of each portal.
|
|
// input - pPortal: the portal we are viewing 'out of' which needs it's corners added to the PVS
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void AddPortalCornersToEnginePVS( CPortal_Base2D* pPortal )
|
|
{
|
|
Assert ( pPortal );
|
|
|
|
if ( !pPortal )
|
|
return;
|
|
|
|
Vector vForward, vRight, vUp;
|
|
pPortal->GetVectors( &vForward, &vRight, &vUp );
|
|
|
|
// Center of the remote portal
|
|
Vector ptOrigin = pPortal->GetAbsOrigin();
|
|
|
|
// Distance offsets to the different edges of the portal... Used in the placement checks
|
|
Vector vToTopEdge = vUp * ( pPortal->GetHalfHeight() - PORTAL_BUMP_FORGIVENESS );
|
|
Vector vToBottomEdge = -vToTopEdge;
|
|
Vector vToRightEdge = vRight * ( pPortal->GetHalfWidth() - PORTAL_BUMP_FORGIVENESS );
|
|
Vector vToLeftEdge = -vToRightEdge;
|
|
|
|
// Distance to place PVS points away from portal, to avoid being in solid
|
|
Vector vForwardBump = vForward * 1.0f;
|
|
|
|
// Add center and edges to the engine PVS
|
|
engine->AddOriginToPVS( ptOrigin + vForwardBump);
|
|
engine->AddOriginToPVS( ptOrigin + vToTopEdge + vToLeftEdge + vForwardBump );
|
|
engine->AddOriginToPVS( ptOrigin + vToTopEdge + vToRightEdge + vForwardBump );
|
|
engine->AddOriginToPVS( ptOrigin + vToBottomEdge + vToLeftEdge + vForwardBump );
|
|
engine->AddOriginToPVS( ptOrigin + vToBottomEdge + vToRightEdge + vForwardBump );
|
|
}
|
|
|
|
void CPortal_Base2D::ComputeSubVisibility( CPVS_Extender **pExtenders, int iExtenderCount, unsigned char *outputPVS, int pvssize, const Vector &vVisOrigin, const VPlane *pVisFrustum, int iVisFrustumPlanes, VisExtensionChain_t *pVisChain, int iAreasNetworked[MAX_MAP_AREAS], int iMaxRecursionsLeft )
|
|
{
|
|
if( iAreasNetworked[MAX_MAP_AREAS - 1] != -1 ) //early out, can't add any more data if we wanted to
|
|
return;
|
|
|
|
if( m_plane_Origin.normal.Dot( vVisOrigin ) < m_plane_Origin.dist )
|
|
return; //vis origin is behind the portal plane
|
|
|
|
//both test if the portal is within the view frustum, and calculate the new one at the same time
|
|
int iFrustumPlanesMax = (iVisFrustumPlanes + GetPolyVertCount() + 1);
|
|
VPlane *pNewFrustum = (VPlane *)stackalloc( sizeof( VPlane ) * iFrustumPlanesMax );
|
|
|
|
int iNewFrustumPlanes = ComputeFrustumThroughPolygon( vVisOrigin, pVisFrustum, iVisFrustumPlanes, pNewFrustum, iFrustumPlanesMax );
|
|
if( iNewFrustumPlanes == 0 )
|
|
{
|
|
//NDebugOverlay::EntityBounds( this, 255, 0, 0, 100, 0.0f );
|
|
return;
|
|
}
|
|
|
|
//NDebugOverlay::EntityBounds( this, 0, 255, 0, 100, 0.0f );
|
|
|
|
CPortal_Base2D *pLinkedPortal = m_hLinkedPortal.Get();
|
|
|
|
int iLinkedArea = pLinkedPortal->NetworkProp()->AreaNum();
|
|
|
|
unsigned char *pLinkedPVS = pLinkedPortal->m_pExtenderData->iPVSBits;
|
|
|
|
if( !pLinkedPortal->m_pExtenderData->bAddedToPVSAlready )
|
|
{
|
|
bool bFound = false;
|
|
for( int i = 0; i != MAX_MAP_AREAS; ++i )
|
|
{
|
|
if( iAreasNetworked[i] == iLinkedArea )
|
|
{
|
|
bFound = true;
|
|
break;
|
|
}
|
|
|
|
if( iAreasNetworked[i] == -1 )
|
|
{
|
|
bFound = true; //we found it by adding it
|
|
iAreasNetworked[i] = iLinkedArea;
|
|
int iOutputPVSIntSize = pvssize / sizeof( unsigned int );
|
|
for( int j = 0; j != iOutputPVSIntSize; ++j )
|
|
{
|
|
((unsigned int *)outputPVS)[j] |= ((unsigned int *)pLinkedPVS)[j];
|
|
}
|
|
for( int j = iOutputPVSIntSize * sizeof( unsigned int ); j != pvssize; ++j )
|
|
{
|
|
outputPVS[j] |= pLinkedPVS[j];
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
AddPortalCornersToEnginePVS( pLinkedPortal );
|
|
pLinkedPortal->m_pExtenderData->bAddedToPVSAlready = true;
|
|
}
|
|
|
|
--iMaxRecursionsLeft;
|
|
if( iMaxRecursionsLeft == 0 )
|
|
return;
|
|
|
|
edict_t *linkedPortalEdict = pLinkedPortal->edict();
|
|
|
|
VisExtensionChain_t chainNode;
|
|
chainNode.m_nArea = iLinkedArea;
|
|
chainNode.pParentChain = pVisChain;
|
|
|
|
|
|
//transform vis origin to linked space
|
|
Vector vTransformedVisOrigin = m_matrixThisToLinked * vVisOrigin;
|
|
|
|
Vector vTranslation = m_matrixThisToLinked.GetTranslation();
|
|
|
|
//transform the planes into the linked portal space
|
|
for( int i = 0; i != iNewFrustumPlanes; ++i )
|
|
{
|
|
pNewFrustum[i].m_Normal = m_matrixThisToLinked.ApplyRotation( pNewFrustum[i].m_Normal );
|
|
pNewFrustum[i].m_Dist += pNewFrustum[i].m_Normal.Dot( vTranslation );
|
|
}
|
|
|
|
Assert( pLinkedPVS != NULL );
|
|
|
|
//extend the vis by what the linked portal can see
|
|
for( int i = 0; i != iExtenderCount; ++i )
|
|
{
|
|
CPVS_Extender *pExtender = pExtenders[i];
|
|
|
|
if ( pExtender->GetExtenderEdict() == linkedPortalEdict )
|
|
continue;
|
|
|
|
if ( pExtender->GetExtenderNetworkProp()->IsInPVS( linkedPortalEdict, pLinkedPVS, (MAX_MAP_LEAFS/8) ) ) //test against linked portal PVS, not aggregate PVS
|
|
{
|
|
chainNode.pExtender = pExtender;
|
|
pExtender->ComputeSubVisibility( pExtenders, iExtenderCount, outputPVS, pvssize, vTransformedVisOrigin, pNewFrustum, iNewFrustumPlanes, &chainNode, iAreasNetworked, iMaxRecursionsLeft );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Notify this the supplied entity has teleported to this portal
|
|
//-----------------------------------------------------------------------------
|
|
void CPortal_Base2D::OnEntityTeleportedToPortal( CBaseEntity *pEntity )
|
|
{
|
|
m_OnEntityTeleportToMe.FireOutput( this, this );
|
|
BroadcastPortalEvent( PORTALEVENT_ENTITY_TELEPORTED_TO );
|
|
|
|
if ( pEntity->IsPlayer() )
|
|
{
|
|
m_OnPlayerTeleportToMe.FireOutput( this, this );
|
|
BroadcastPortalEvent( PORTALEVENT_PLAYER_TELEPORTED_TO );
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Notify this the supplied entity has teleported from this portal
|
|
//-----------------------------------------------------------------------------
|
|
void CPortal_Base2D::OnEntityTeleportedFromPortal( CBaseEntity *pEntity )
|
|
{
|
|
m_OnEntityTeleportFromMe.FireOutput( this, this );
|
|
BroadcastPortalEvent( PORTALEVENT_ENTITY_TELEPORTED_FROM );
|
|
|
|
if ( pEntity->IsPlayer() )
|
|
{
|
|
m_OnPlayerTeleportFromMe.FireOutput( this, this );
|
|
BroadcastPortalEvent( PORTALEVENT_PLAYER_TELEPORTED_FROM );
|
|
}
|
|
}
|
|
|
|
|
|
void EntityPortalled( CPortal_Base2D *pPortal, CBaseEntity *pOther, const Vector &vNewOrigin, const QAngle &qNewAngles, bool bForcedDuck )
|
|
{
|
|
/*if( pOther->IsPlayer() )
|
|
{
|
|
Warning( "Server player portalled %f %f %f %f %f %f %f\n", gpGlobals->curtime, XYZ( vNewOrigin ), XYZ( ((CPortal_Player *)pOther)->pl.v_angle ) );
|
|
}*/
|
|
|
|
for( int i = 1; i <= gpGlobals->maxClients; ++i )
|
|
{
|
|
CPortal_Player *pPlayer = (CPortal_Player *)UTIL_PlayerByIndex( i );
|
|
if( pPlayer )
|
|
{
|
|
pPlayer->NetworkPortalTeleportation( pOther, pPortal, gpGlobals->curtime, bForcedDuck );
|
|
}
|
|
}
|
|
}
|
|
|
|
void DebugPortalCollideables_f( const CCommand &command )
|
|
{
|
|
for( int i = 0; i != CPortal_Base2D_Shared::AllPortals.Count(); ++i )
|
|
{
|
|
CPortal_Base2D *pPortal = CPortal_Base2D_Shared::AllPortals[i];
|
|
pPortal->m_PortalSimulator.DebugCollisionOverlay( true, 30.0f );
|
|
}
|
|
}
|
|
|
|
static ConCommand debugportalcollideables("debugportalcollideables", DebugPortalCollideables_f, "Dump all CPhysCollides for all portals to the debug overlay" );
|