996 lines
32 KiB
C++
996 lines
32 KiB
C++
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "cbase.h"
|
|
#include "portal_base2d_shared.h"
|
|
#include "portal_shareddefs.h"
|
|
#include "mathlib/polyhedron.h"
|
|
#include "tier1/callqueue.h"
|
|
#include "debugoverlay_shared.h"
|
|
#include "collisionutils.h"
|
|
|
|
#if defined( CLIENT_DLL )
|
|
#include "c_portal_player.h"
|
|
#else
|
|
#include "portal_player.h"
|
|
#include "portal_physics_collisionevent.h"
|
|
#endif
|
|
|
|
#ifdef CLIENT_DLL
|
|
#include "c_basedoor.h"
|
|
#include "prediction.h"
|
|
#else
|
|
#include "env_debughistory.h"
|
|
#include "portal/weapon_physcannon.h"
|
|
#include "physics_bone_follower.h"
|
|
#include "projectedwallentity.h"
|
|
#include "portal_physics_collisionevent.h"
|
|
#endif
|
|
|
|
CUtlVector<CPortal_Base2D *> CPortal_Base2D_Shared::AllPortals;
|
|
|
|
void MobilePortalsUpdatedCallback( IConVar *var, const char *pOldValue, float flOldValue );
|
|
ConVar sv_allow_mobile_portals( "sv_allow_mobile_portals", "0", FCVAR_REPLICATED, "", MobilePortalsUpdatedCallback );
|
|
|
|
void MobilePortalsUpdatedCallback( IConVar *var, const char *pOldValue, float flOldValue )
|
|
{
|
|
if ( sv_allow_mobile_portals.GetFloat() == flOldValue )
|
|
return;
|
|
|
|
static ConVarRef sv_cheats ( "sv_cheats" );
|
|
bool bCheatsAllowed = (sv_cheats.IsValid() && sv_cheats.GetBool() );
|
|
|
|
if ( !bCheatsAllowed )
|
|
{
|
|
#ifdef CLIENT_DLL
|
|
if ( V_stricmp( engine->GetLevelNameShort(), "sp_a2_bts5" ) == 0 )
|
|
#else
|
|
if ( V_stricmp( gpGlobals->mapname.ToCStr(), "sp_a2_bts5" ) == 0 )
|
|
#endif
|
|
{
|
|
bCheatsAllowed = true;
|
|
}
|
|
}
|
|
|
|
if ( !bCheatsAllowed )
|
|
{
|
|
var->SetValue( 0 );
|
|
}
|
|
}
|
|
|
|
ConVar sv_allow_mobile_portal_teleportation( "sv_allow_mobile_portal_teleportation", "1", FCVAR_CHEAT | FCVAR_REPLICATED );
|
|
ConVar sv_portal_unified_velocity( "sv_portal_unified_velocity", "1", FCVAR_CHEAT | FCVAR_REPLICATED, "An attempt at removing patchwork velocity tranformation in portals, moving to a unified approach." );
|
|
ConVar sv_bowie_maneuver_threshold( "sv_bowie_maneuver_threshold", "375.0f", FCVAR_CHEAT | FCVAR_REPLICATED );
|
|
ConVar sv_futbol_floor_exit_angle( "sv_futbol_floor_exit_angle", "85", FCVAR_CHEAT | FCVAR_REPLICATED );
|
|
extern ConVar sv_portal_debug_touch;
|
|
|
|
extern CCallQueue *GetPortalCallQueue();
|
|
|
|
#if defined( GAME_DLL )
|
|
extern CPortal_CollisionEvent g_Collisions;
|
|
ConVar sv_portal_teleportation_resets_collision_events( "sv_portal_teleportation_resets_collision_events", "1", FCVAR_CHEAT );
|
|
#endif
|
|
|
|
void CPortal_Base2D_Shared::UpdatePortalTransformationMatrix( const matrix3x4_t &localToWorld, const matrix3x4_t &remoteToWorld, VMatrix *pMatrix )
|
|
{
|
|
VMatrix matPortal1ToWorldInv, matRotation;
|
|
|
|
//inverse of this
|
|
MatrixInverseTR( VMatrix( localToWorld ), matPortal1ToWorldInv );
|
|
|
|
//180 degree rotation about up
|
|
matRotation.Identity();
|
|
matRotation.m[0][0] = -1.0f;
|
|
matRotation.m[1][1] = -1.0f;
|
|
|
|
VMatrix vTest = matRotation * matPortal1ToWorldInv;
|
|
|
|
//final
|
|
VMatrix matPortal2ToWorld( remoteToWorld );
|
|
*pMatrix = matPortal2ToWorld * matRotation * matPortal1ToWorldInv;
|
|
}
|
|
|
|
static char *g_pszPortalNonTeleportable[] =
|
|
{
|
|
"func_door",
|
|
"func_door_rotating",
|
|
"prop_door_rotating",
|
|
"func_movelinear",
|
|
"func_tracktrain",
|
|
//"env_ghostanimating",
|
|
"physicsshadowclone",
|
|
"prop_ragdoll",
|
|
"physics_prop_ragdoll",
|
|
"func_brush"
|
|
};
|
|
|
|
bool CPortal_Base2D_Shared::IsEntityTeleportable( CBaseEntity *pEntity )
|
|
{
|
|
switch( pEntity->GetMoveType() )
|
|
{
|
|
case MOVETYPE_NONE:
|
|
case MOVETYPE_PUSH:
|
|
return false;
|
|
};
|
|
|
|
if( pEntity->GetMoveParent() != NULL )
|
|
return false;
|
|
|
|
#ifdef CLIENT_DLL
|
|
//client
|
|
|
|
if( dynamic_cast<C_BaseDoor *>(pEntity) != NULL )
|
|
return false;
|
|
|
|
#else
|
|
//server
|
|
if( dynamic_cast<CBoneFollower *>(pEntity) != NULL )
|
|
return false;
|
|
|
|
CPortal_Player *pHoldingPlayer = (CPortal_Player *)GetPlayerHoldingEntity( pEntity );
|
|
if ( pHoldingPlayer && pHoldingPlayer->IsUsingVMGrab() )
|
|
{
|
|
// No need to teleport with viewmodel grab controller
|
|
return false;
|
|
}
|
|
|
|
for( int i = 0; i != ARRAYSIZE(g_pszPortalNonTeleportable); ++i )
|
|
{
|
|
if( FClassnameIs( pEntity, g_pszPortalNonTeleportable[i] ) )
|
|
return false;
|
|
}
|
|
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
static char *g_pszPortalPhysicsCloneTouching[] =
|
|
{
|
|
"func_brush",
|
|
"projected_wall_entity"
|
|
};
|
|
|
|
bool CPortal_Base2D_Shared::ShouldPhysicsCloneNonTeleportableEntityAcrossPortals( CBaseEntity *pEntity )
|
|
{
|
|
for( int i = 0; i != ARRAYSIZE(g_pszPortalPhysicsCloneTouching); ++i )
|
|
{
|
|
if( FClassnameIs( pEntity, g_pszPortalPhysicsCloneTouching[i] ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//unify how we determine the velocity of objects when portalling them
|
|
Vector Portal_FindUsefulVelocity( CBaseEntity *pOther )
|
|
{
|
|
Vector vOtherVelocity;
|
|
IPhysicsObject *pOtherPhysObject = pOther->VPhysicsGetObject();
|
|
if( sv_portal_unified_velocity.GetBool() )
|
|
{
|
|
if( (pOther->GetMoveType() == MOVETYPE_VPHYSICS) && (pOtherPhysObject != NULL) )
|
|
{
|
|
pOtherPhysObject->GetVelocity( &vOtherVelocity, NULL );
|
|
}
|
|
else
|
|
{
|
|
vOtherVelocity = pOther->GetAbsVelocity();
|
|
if( pOtherPhysObject )
|
|
{
|
|
Vector vPhysVelocity;
|
|
pOtherPhysObject->GetVelocity( &vPhysVelocity, NULL );
|
|
|
|
if( vPhysVelocity.LengthSqr() > vOtherVelocity.LengthSqr() )
|
|
{
|
|
vOtherVelocity = vPhysVelocity;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( pOther->GetMoveType() == MOVETYPE_VPHYSICS )
|
|
{
|
|
if( pOtherPhysObject && (pOtherPhysObject->GetShadowController() == NULL) )
|
|
{
|
|
pOtherPhysObject->GetVelocity( &vOtherVelocity, NULL );
|
|
}
|
|
else
|
|
{
|
|
#if defined( GAME_DLL )
|
|
pOther->GetVelocity( &vOtherVelocity );
|
|
#else
|
|
vOtherVelocity = pOther->GetAbsVelocity();
|
|
#endif
|
|
}
|
|
}
|
|
else if ( pOther->IsPlayer() && pOther->VPhysicsGetObject() )
|
|
{
|
|
pOther->VPhysicsGetObject()->GetVelocity( &vOtherVelocity, NULL );
|
|
|
|
if ( vOtherVelocity == vec3_origin )
|
|
{
|
|
vOtherVelocity = pOther->GetAbsVelocity();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
#if defined( GAME_DLL )
|
|
pOther->GetVelocity( &vOtherVelocity );
|
|
#else
|
|
vOtherVelocity = pOther->GetAbsVelocity();
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
return vOtherVelocity;
|
|
}
|
|
|
|
|
|
bool CPortal_Base2D::ShouldTeleportTouchingEntity( CBaseEntity *pOther )
|
|
{
|
|
if( m_hLinkedPortal.Get() == NULL )
|
|
{
|
|
#if defined( GAME_DLL )
|
|
#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() );
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool bMobilePortals = IsMobile() || m_hLinkedPortal->IsMobile();
|
|
if( bMobilePortals && !sv_allow_mobile_portal_teleportation.GetBool() )
|
|
return false;
|
|
|
|
//can't teleport an entity we don't own, unless this is a mobile portal interaction
|
|
if( !m_PortalSimulator.OwnsEntity(pOther) && !bMobilePortals )
|
|
{
|
|
#if defined( GAME_DLL )
|
|
#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 );
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
if( !CPortal_Base2D_Shared::IsEntityTeleportable( pOther ) )
|
|
return false;
|
|
|
|
//Vector ptOtherOrigin = pOther->GetAbsOrigin();
|
|
Vector ptOtherCenter = pOther->WorldSpaceCenter();
|
|
Vector vOtherVelocity = Portal_FindUsefulVelocity( pOther );
|
|
vOtherVelocity -= GetAbsVelocity(); //subtract the portal's velocity if it's moving. It's all relative.
|
|
|
|
if( vOtherVelocity.Dot( m_PortalSimulator.GetInternalData().Placement.vForward ) > 0.0f )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if( bMobilePortals )
|
|
{
|
|
return pOther->IsPlayer() && (vOtherVelocity.Dot( m_PortalSimulator.GetInternalData().Placement.vForward ) < -5.0f); //only allow players for now in mobile portal teleportations
|
|
}
|
|
|
|
// Test for entity's center being past portal plane
|
|
if(m_PortalSimulator.GetInternalData().Placement.PortalPlane.m_Normal.Dot( ptOtherCenter ) < m_PortalSimulator.GetInternalData().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;
|
|
}
|
|
#if defined( GAME_DLL )
|
|
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() );
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void CPortal_Base2D::TeleportTouchingEntity( CBaseEntity *pOther )
|
|
{
|
|
if ( GetPortalCallQueue() )
|
|
{
|
|
GetPortalCallQueue()->QueueCall( this, &CPortal_Base2D::TeleportTouchingEntity, pOther );
|
|
return;
|
|
}
|
|
|
|
PreTeleportTouchingEntity( pOther );
|
|
|
|
bool bMobilePortals = IsMobile() || m_hLinkedPortal->IsMobile();
|
|
|
|
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;
|
|
Warning( "PORTALLING PLAYER SHOULD BE DONE IN GAMEMOVEMENT\n" );
|
|
}
|
|
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;
|
|
|
|
vOtherVelocity = Portal_FindUsefulVelocity( pOther );
|
|
vOtherVelocity -= GetAbsVelocity(); //subtract the portal's velocity if it's moving. It's all relative.
|
|
|
|
const PS_InternalData_t &RemotePortalDataAccess = m_hLinkedPortal->m_PortalSimulator.GetInternalData();
|
|
const PS_InternalData_t &LocalPortalDataAccess = m_PortalSimulator.GetInternalData();
|
|
|
|
#if defined( GAME_DLL )
|
|
bool bCrouchPlayer = false;
|
|
#endif
|
|
if( bPlayer )
|
|
{
|
|
qOtherAngles = pOtherAsPlayer->EyeAngles();
|
|
bNonPhysical = true;
|
|
Vector vWorldUp( 0.0f, 0.0f, 1.0f );
|
|
vWorldUp = m_matrixThisToLinked.ApplyRotation( vWorldUp );
|
|
|
|
if( fabs( vWorldUp.z ) < 0.7071f ) //the transformation will change our notion of UP significantly
|
|
{
|
|
//we have to compensate for the fact that AABB's don't rotate ever
|
|
pOtherAsPlayer->SetGroundEntity( NULL );
|
|
|
|
//curl the player up into a little ball
|
|
#if defined( GAME_DLL )
|
|
if( !pOtherAsPlayer->IsDucked() )
|
|
{
|
|
bCrouchPlayer = true;
|
|
ptOtherOrigin.z += 18.0f;
|
|
//pOtherAsPlayer->ForceDuckThisFrame(); NOTE: This should never be used! Players teleport in game movement!!!
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
qOtherAngles = pOther->GetAbsAngles();
|
|
bNonPhysical = FClassnameIs( pOther, "prop_energy_ball" );
|
|
}
|
|
|
|
if( bMobilePortals ) //hack the position a bit so it pops out as far in front of the remote portal as it is in front of the local portal
|
|
{
|
|
float fPlaneDist = LocalPortalDataAccess.Placement.PortalPlane.DistTo( ptOtherCenter );
|
|
Vector vOffset = LocalPortalDataAccess.Placement.vForward * (-(fPlaneDist * 2.0f)); //equidistant from the portal plane, but on the opposite side
|
|
ptOtherCenter += vOffset;
|
|
ptOtherOrigin += vOffset;
|
|
//NDebugOverlay::Box( ptOtherCenter, Vector( -10.f, -10.f, -10.0f ), Vector( 10.0f, 10.0f, 10.0f ), 0, 255, 0, 128, 10.0f );
|
|
}
|
|
|
|
|
|
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 );
|
|
|
|
// TODO: For accuracy sake we should handle this for all portal orientations
|
|
// It's only abusable for floor to floor though.
|
|
if ( IsFloorPortal( 0.9f ) && m_hLinkedPortal->IsFloorPortal( 0.9f ) )
|
|
{
|
|
// When we teleported we were assuming that our center was exactly on the same plane as the portal
|
|
// We need to see how far off we were from that, Then we need to *double* compensate to push us
|
|
// exactly as far off in our new position as we were before.
|
|
ptNewOrigin.z -= 2.0f*( GetAbsOrigin() - ptOtherCenter ).z;
|
|
}
|
|
}
|
|
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 );
|
|
|
|
QAngle savedAngles = qNewAngles;
|
|
|
|
// Our teleport is going to roll us. See if we could find an over extended angle that would prevent a roll since that's the most disorienting thing.
|
|
if( bPlayer && qNewAngles[ROLL] != 0.0f )
|
|
{
|
|
float bestAngle = fabs( 180.f - qNewAngles.z );
|
|
float punchMag = 0.0f;
|
|
QAngle punchAngles;
|
|
|
|
// try to adjust the initial angle to see if we can get something better
|
|
for ( int i = 0; i < 20; i++ )
|
|
{
|
|
QAngle qTestAngles = qOtherAngles;
|
|
float pitchAdjust = 5.f*(1 + i/2);
|
|
if( i%2 == 1 )
|
|
{
|
|
pitchAdjust *= -1.f;
|
|
}
|
|
qTestAngles[PITCH] += pitchAdjust;
|
|
|
|
qTestAngles = TransformAnglesToWorldSpace( qTestAngles, m_matrixThisToLinked.As3x4() );
|
|
|
|
qTestAngles.x = AngleNormalizePositive( qTestAngles.x );
|
|
qTestAngles.y = AngleNormalizePositive( qTestAngles.y );
|
|
qTestAngles.z = AngleNormalizePositive( qTestAngles.z );
|
|
|
|
|
|
if( fabs( 180.f - qTestAngles.z ) > (bestAngle + 1.1f*fabs(pitchAdjust)) )
|
|
{
|
|
bestAngle = fabs( 180.f - qTestAngles.z );
|
|
qNewAngles = qTestAngles;
|
|
punchMag = pitchAdjust;
|
|
}
|
|
}
|
|
|
|
punchAngles = savedAngles - qNewAngles;
|
|
|
|
if( punchAngles.y < 180.f )
|
|
punchAngles.y += 360.f;
|
|
if( punchAngles.y > 180.f )
|
|
punchAngles.y -= 360.f;
|
|
|
|
if( fabs( punchAngles.y ) > 120.f )
|
|
{
|
|
punchAngles.z = 0.f;
|
|
punchAngles.y = 0.f;
|
|
punchAngles.x = -punchMag;
|
|
}
|
|
|
|
pOtherAsPlayer->SetPunchAngle( punchAngles );
|
|
}
|
|
|
|
// Reorient the velocity
|
|
vNewVelocity = m_matrixThisToLinked.ApplyRotation( vOtherVelocity );
|
|
}
|
|
|
|
//velocity hacks
|
|
{
|
|
float fExitMin, fExitMax;
|
|
CPortal_Base2D::GetExitSpeedRange( this, bPlayer, fExitMin, fExitMax, ptNewOrigin, pOther );
|
|
|
|
float fSpeed = vNewVelocity.Length();
|
|
if( fSpeed == 0.0f )
|
|
{
|
|
if( fExitMin >= 0.0f )
|
|
{
|
|
vNewVelocity = m_hLinkedPortal->m_vForward * fExitMin;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( fSpeed < fExitMin )
|
|
{
|
|
vNewVelocity *= (fExitMin / fSpeed);
|
|
}
|
|
else if( fSpeed > fExitMax )
|
|
{
|
|
vNewVelocity *= (fExitMax / fSpeed);
|
|
}
|
|
}
|
|
|
|
if( bPlayer )
|
|
{
|
|
bool bPitchReorientation = false;
|
|
if ( IsFloorPortal( 0.9f ) && m_hLinkedPortal->IsFloorPortal( 0.9f ) ) //floor to floor transition
|
|
{
|
|
Vector vPlayerEyeForward;
|
|
pOtherAsPlayer->EyeVectors( &vPlayerEyeForward );
|
|
if( vPlayerEyeForward.z > fabs( 0.85f ) ) //player looking mostly down into entrance portal
|
|
{
|
|
//do a pitch reorientation instead of a corkscrew roll
|
|
bPitchReorientation = true;
|
|
}
|
|
|
|
const float cfBowieManueverThreshold = sv_bowie_maneuver_threshold.GetFloat();
|
|
if( vOtherVelocity.LengthSqr() < (cfBowieManueverThreshold * cfBowieManueverThreshold) ) //velocity below the threshold where we'd like to do the David Bowie move from Labyrinth where he was walking around the crazy Escher style room
|
|
{
|
|
//compute what direction we'll be facing coming out of the portal, but projected onto the exit portal plane and normalized
|
|
Vector vPlayerForward;
|
|
pOtherAsPlayer->GetVectors( &vPlayerForward, NULL, NULL );
|
|
Vector vBowieForward = LocalPortalDataAccess.Placement.matThisToLinked.ApplyRotation( vPlayerForward );
|
|
vBowieForward = vBowieForward - (RemotePortalDataAccess.Placement.vForward * vBowieForward.Dot( RemotePortalDataAccess.Placement.vForward )); //project onto the exit portal plane
|
|
vBowieForward.NormalizeInPlace();
|
|
|
|
// negate our forward since we are always going to be facing the wrong way when we come out.
|
|
vBowieForward = -vBowieForward;
|
|
|
|
//figure out which axis our forward is most aligned with, then base magnitude on how far we'd have to go to reach the portal border on that side
|
|
float fGoToSide = fabs( vBowieForward.Dot( RemotePortalDataAccess.Placement.vRight ) );
|
|
float fGoToBottom = fabs( vBowieForward.Dot( -RemotePortalDataAccess.Placement.vUp ) );
|
|
float fPushMagnitude;
|
|
if( fGoToSide > fGoToBottom )
|
|
{
|
|
fPushMagnitude = RemotePortalDataAccess.Placement.fHalfWidth / fGoToSide;
|
|
}
|
|
else
|
|
{
|
|
fPushMagnitude = RemotePortalDataAccess.Placement.fHalfHeight / fGoToBottom;
|
|
}
|
|
|
|
//calculate existing velocity perpendicular to the exit plane
|
|
Vector vExistingPlanarVelocity = vNewVelocity - RemotePortalDataAccess.Placement.vForward * vNewVelocity.Dot( RemotePortalDataAccess.Placement.vForward );
|
|
|
|
// if the Bowie maneuver velocity would be greater than our existing coplanar velocity, cancel out the existing coplanar velocity and add in the Bowie maneuver velocity
|
|
// if( 2.0f*(fPushMagnitude * fPushMagnitude) > vExistingPlanarVelocity.LengthSqr() ) // we really prefer to bowie, force this for now.
|
|
{
|
|
// We don't want to keep spinning - give us an extra 20% to make sure we get out.
|
|
Vector vecAdded = 1.2f*((vBowieForward * fPushMagnitude) - vExistingPlanarVelocity);
|
|
vNewVelocity += vecAdded;
|
|
}
|
|
}
|
|
}
|
|
|
|
pOtherAsPlayer->m_bPitchReorientation = bPitchReorientation;
|
|
}
|
|
}
|
|
|
|
#if defined( GAME_DLL )
|
|
//both changing portal environment and teleportation can trigger penetration solving. Disable solving while we're performing the migration and teleportations as we'll be in a bad state until both complete.
|
|
CPortal_CollisionEvent::DisablePenetrationSolving_Push( true );
|
|
#endif
|
|
|
|
//untouch the portal(s), will force a touch on destination after the teleport
|
|
if( !bMobilePortals )
|
|
{
|
|
m_PortalSimulator.ReleaseOwnershipOfEntity( pOther, true );
|
|
|
|
m_hLinkedPortal->m_PortalSimulator.TakeOwnershipOfEntity( pOther );
|
|
|
|
//m_hLinkedPortal->PhysicsNotifyOtherOfUntouch( m_hLinkedPortal, pOther );
|
|
//pOther->PhysicsNotifyOtherOfUntouch( pOther, m_hLinkedPortal );
|
|
}
|
|
|
|
#if defined( GAME_DLL )
|
|
if( sv_portal_debug_touch.GetBool() )
|
|
{
|
|
DevMsg( "===PORTAL %i TELEPORTING: %s : %f %f %f : %f===\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), vOtherVelocity.x, vOtherVelocity.y, vOtherVelocity.z, gpGlobals->curtime );
|
|
}
|
|
#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
|
|
#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;
|
|
|
|
#if defined( GAME_DLL )
|
|
pOtherAsPlayer->pl.fixangle = FIXANGLE_ABSOLUTE;
|
|
pOtherAsPlayer->UpdateVPhysicsPosition( ptNewOrigin, vNewVelocity, 0.0f );
|
|
pOtherAsPlayer->Teleport( &ptNewOrigin, &qNewAngles, &vNewVelocity );
|
|
#else
|
|
pOtherAsPlayer->SetAbsOrigin( ptNewOrigin );
|
|
pOtherAsPlayer->SetAbsAngles( qNewAngles );
|
|
pOtherAsPlayer->SetAbsVelocity( vNewVelocity );
|
|
#endif
|
|
|
|
|
|
//pOtherAsPlayer->UnDuck();
|
|
|
|
//pOtherAsPlayer->m_angEyeAngles = qTransformedEyeAngles;
|
|
//pOtherAsPlayer->pl.v_angle = qTransformedEyeAngles;
|
|
//pOtherAsPlayer->pl.fixangle = FIXANGLE_ABSOLUTE;
|
|
}
|
|
else
|
|
{
|
|
if( bNonPhysical )
|
|
{
|
|
#if defined( GAME_DLL )
|
|
pOther->Teleport( &ptNewOrigin, &qNewAngles, &vNewVelocity );
|
|
#else
|
|
pOther->SetAbsOrigin( ptNewOrigin );
|
|
pOther->SetAbsAngles( qNewAngles );
|
|
pOther->SetAbsVelocity( vNewVelocity );
|
|
#endif
|
|
}
|
|
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
|
|
#if defined( GAME_DLL )
|
|
pOther->Teleport( &ptNewOrigin, &qNewAngles, &vec3_origin );
|
|
#else
|
|
pOther->SetAbsOrigin( ptNewOrigin );
|
|
pOther->SetAbsAngles( qNewAngles );
|
|
pOther->SetAbsVelocity( vec3_origin );
|
|
#endif
|
|
// Hacks for the Wheatley battle. We need the futbols to be portalled in an intuitive way.
|
|
if( FClassnameIs( pOther, "prop_exploding_futbol" ) )
|
|
{
|
|
// Exiting a floor portal: Always come out headed towards the top of the exit portal
|
|
if( m_hLinkedPortal->m_vForward.z == 1.f )
|
|
{
|
|
vNewVelocity = m_hLinkedPortal->m_vUp * vNewVelocity.Length();
|
|
|
|
// Build our pitch matrix
|
|
Vector vRotAxis = m_hLinkedPortal->m_vRight;
|
|
VMatrix mtxRotation;
|
|
float flRotationAmt = -sv_futbol_floor_exit_angle.GetFloat();
|
|
MatrixBuildRotationAboutAxis( mtxRotation, vRotAxis, flRotationAmt );
|
|
|
|
vNewVelocity = mtxRotation * vNewVelocity;
|
|
}
|
|
// Exiting a wall portal: Shoot straight out
|
|
else
|
|
{
|
|
// Magic number so the bombs will fly the same speed everytime
|
|
vNewVelocity = m_hLinkedPortal->m_vForward * 850.f;
|
|
}
|
|
|
|
}
|
|
|
|
pOther->ApplyAbsVelocityImpulse( vNewVelocity );
|
|
|
|
}
|
|
}
|
|
|
|
pOther->RemoveEffects( EF_NOINTERP );
|
|
}
|
|
|
|
#if defined( GAME_DLL )
|
|
CPortal_CollisionEvent::DisablePenetrationSolving_Pop(); //re-enable penetration solving now that we're in the target environment and at the target position
|
|
if( sv_portal_teleportation_resets_collision_events.GetBool() )
|
|
{
|
|
g_Collisions.RemovePenetrationEvents( pOther );
|
|
}
|
|
#endif
|
|
|
|
if( bMobilePortals )
|
|
{
|
|
FindClosestPassableSpace( pOther, LocalPortalDataAccess.Placement.vForward );
|
|
}
|
|
|
|
#if defined( GAME_DLL )
|
|
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 );
|
|
}
|
|
|
|
CGrabController *pController = GetGrabControllerForPlayer( pHoldingPlayer );
|
|
if( pController )
|
|
{
|
|
pController->CheckPortalOscillation( this, pOther, pHoldingPlayer );
|
|
}
|
|
|
|
for ( int i = 0; i < IProjectedWallEntityAutoList::AutoList().Count(); ++i )
|
|
{
|
|
CProjectedWallEntity *pWall = static_cast< CProjectedWallEntity* >( IProjectedWallEntityAutoList::AutoList()[i] );
|
|
pWall->DisplaceObstructingEntity( pOther, true );
|
|
}
|
|
|
|
// For alternate ticks, if we're going to simulate physics again make sure the grab controller's target position is up to date with the
|
|
// teleported object position.
|
|
if ( PhysIsFinalTick() == false )
|
|
{
|
|
UpdateGrabControllerTargetPosition( pHoldingPlayer, NULL, NULL, true );
|
|
}
|
|
}
|
|
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 );
|
|
}
|
|
#endif
|
|
|
|
//force the entity to be touching the other portal right this millisecond
|
|
if( !bMobilePortals )
|
|
{
|
|
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 );
|
|
}
|
|
|
|
#if defined( GAME_DLL )
|
|
// 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 the portals to fire appropriate outputs
|
|
OnEntityTeleportedFromPortal( pOther );
|
|
if ( m_hLinkedPortal )
|
|
{
|
|
m_hLinkedPortal->OnEntityTeleportedToPortal( pOther );
|
|
}
|
|
|
|
//notify clients of the teleportation
|
|
EntityPortalled( this, pOther, ptNewOrigin, qNewAngles, false );
|
|
#endif
|
|
|
|
#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
|
|
|
|
#if defined( GAME_DLL )
|
|
pOther->NetworkProp()->NetworkStateForceUpdate();
|
|
if( bPlayer )
|
|
pOtherAsPlayer->pl.NetworkStateChanged();
|
|
#endif
|
|
|
|
//if( bPlayer )
|
|
// NDebugOverlay::EntityBounds( pOther, 0, 255, 0, 128, 60.0f );
|
|
|
|
Assert( (bPlayer == false) || (pOtherAsPlayer->m_hPortalEnvironment.Get() == m_hLinkedPortal.Get()) );
|
|
|
|
PostTeleportTouchingEntity( pOther );
|
|
|
|
#if defined( CLIENT_DLL ) && 0 //debugging code
|
|
{
|
|
Vector vPos = pOther->GetAbsOrigin();
|
|
Warning( "CPortal_Base2D::TeleportTouchingEntity(%s), portal %d, time %f %f, End Position: %f %f %f\n", pOther->GetClassname(), m_bIsPortal2 ? 2 : 1, gpGlobals->curtime, prediction->GetSavedTime(), vPos.x, vPos.y, vPos.z );
|
|
NDebugOverlay::EntityBounds( pOther, 0, 0, 255, 100, 5.0f );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the portal active
|
|
//-----------------------------------------------------------------------------
|
|
void CPortal_Base2D::SetActive( bool bActive )
|
|
{
|
|
m_bOldActivatedState = m_bActivated;
|
|
m_bActivated = bActive;
|
|
}
|
|
|
|
bool CPortal_Base2D::IsActivedAndLinked( void ) const
|
|
{
|
|
return ( IsActive() && (m_hLinkedPortal.Get() != NULL) && (m_hLinkedPortal.Get()->IsActive()) );
|
|
}
|
|
|
|
bool CPortal_Base2D::IsFloorPortal( float fThreshold ) const
|
|
{
|
|
return m_PortalSimulator.GetInternalData().Placement.vForward.z > fThreshold;
|
|
}
|
|
|
|
bool CPortal_Base2D::IsCeilingPortal( float fThreshold ) const
|
|
{
|
|
return m_PortalSimulator.GetInternalData().Placement.vForward.z < fThreshold;
|
|
}
|
|
|
|
void CPortal_Base2D::PortalSimulator_TookOwnershipOfEntity( CBaseEntity *pEntity )
|
|
{
|
|
if( pEntity->IsPlayer() )
|
|
{
|
|
//DevMsg( "Portal %i simulator took ownership of player: %f\n", ((m_bIsPortal2)?(2):(1)), gpGlobals->curtime );
|
|
((CPortal_Player *)pEntity)->m_hPortalEnvironment = this;
|
|
}
|
|
}
|
|
|
|
void CPortal_Base2D::PortalSimulator_ReleasedOwnershipOfEntity( CBaseEntity *pEntity )
|
|
{
|
|
if( pEntity->IsPlayer() && (((CPortal_Player *)pEntity)->m_hPortalEnvironment.Get() == this) )
|
|
{
|
|
//DevMsg( "Portal %i simulator released ownership of player: %f\n", ((m_bIsPortal2)?(2):(1)), gpGlobals->curtime );
|
|
((CPortal_Player *)pEntity)->m_hPortalEnvironment = NULL;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
void CPortal_Base2D::UpdateCollisionShape( void )
|
|
{
|
|
if( m_pCollisionShape )
|
|
{
|
|
physcollision->DestroyCollide( m_pCollisionShape );
|
|
m_pCollisionShape = NULL;
|
|
}
|
|
|
|
Vector vLocalMins = GetLocalMins();
|
|
Vector vLocalMaxs = GetLocalMaxs();
|
|
|
|
if( (vLocalMaxs.x <= vLocalMins.x) || (vLocalMaxs.y <= vLocalMins.y) || (vLocalMaxs.z <= vLocalMins.z) )
|
|
return; //volume is 0 (or less)
|
|
|
|
|
|
//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] = vLocalMaxs.x;
|
|
|
|
fPlanes[(1*4) + 0] = -1.0f;
|
|
fPlanes[(1*4) + 1] = 0.0f;
|
|
fPlanes[(1*4) + 2] = 0.0f;
|
|
fPlanes[(1*4) + 3] = -vLocalMins.x;
|
|
|
|
fPlanes[(2*4) + 0] = 0.0f;
|
|
fPlanes[(2*4) + 1] = 1.0f;
|
|
fPlanes[(2*4) + 2] = 0.0f;
|
|
fPlanes[(2*4) + 3] = vLocalMaxs.y;
|
|
|
|
fPlanes[(3*4) + 0] = 0.0f;
|
|
fPlanes[(3*4) + 1] = -1.0f;
|
|
fPlanes[(3*4) + 2] = 0.0f;
|
|
fPlanes[(3*4) + 3] = -vLocalMins.y;
|
|
|
|
fPlanes[(4*4) + 0] = 0.0f;
|
|
fPlanes[(4*4) + 1] = 0.0f;
|
|
fPlanes[(4*4) + 2] = 1.0f;
|
|
fPlanes[(4*4) + 3] = vLocalMaxs.z;
|
|
|
|
fPlanes[(5*4) + 0] = 0.0f;
|
|
fPlanes[(5*4) + 1] = 0.0f;
|
|
fPlanes[(5*4) + 2] = -1.0f;
|
|
fPlanes[(5*4) + 3] = -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 );
|
|
}
|
|
|
|
|
|
float CPortal_Base2D::GetMinimumExitSpeed( bool bPlayer, bool bEntranceOnFloor, bool bExitOnFloor, const Vector &vEntityCenterAtExit, CBaseEntity *pEntity )
|
|
{
|
|
return -FLT_MAX; //default behavior is to not mess with the speed
|
|
}
|
|
|
|
float CPortal_Base2D::GetMaximumExitSpeed( bool bPlayer, bool bEntranceOnFloor, bool bExitOnFloor, const Vector &vEntityCenterAtExit, CBaseEntity *pEntity )
|
|
{
|
|
return FLT_MAX; //default behavior is to not mess with the speed
|
|
}
|
|
|
|
|
|
void CPortal_Base2D::GetExitSpeedRange( CPortal_Base2D *pEntrancePortal, bool bPlayer, float &fExitMinimum, float &fExitMaximum, const Vector &vEntityCenterAtExit, CBaseEntity *pEntity )
|
|
{
|
|
CPortal_Base2D *pExitPortal = pEntrancePortal ? pEntrancePortal->m_hLinkedPortal.Get() : NULL;
|
|
if( !pExitPortal )
|
|
{
|
|
fExitMinimum = -FLT_MAX;
|
|
fExitMaximum = FLT_MAX;
|
|
return;
|
|
}
|
|
|
|
const float COS_PI_OVER_SIX = 0.86602540378443864676372317075294f; // cos( 30 degrees ) in radians
|
|
bool bEntranceOnFloor = pEntrancePortal->m_plane_Origin.normal.z > COS_PI_OVER_SIX;
|
|
bool bExitOnFloor = pExitPortal->m_plane_Origin.normal.z > COS_PI_OVER_SIX;
|
|
|
|
fExitMinimum = pExitPortal->GetMinimumExitSpeed( bPlayer, bEntranceOnFloor, bExitOnFloor, vEntityCenterAtExit, pEntity );
|
|
fExitMaximum = pExitPortal->GetMaximumExitSpeed( bPlayer, bEntranceOnFloor, bExitOnFloor, vEntityCenterAtExit, pEntity );
|
|
}
|
|
|
|
|