Team Fortress 2 Source Code as on 22/4/2020
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.
 
 
 
 
 
 

343 lines
11 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Entity which teleports touched entities and reorients their physics
//
//=============================================================================//
#include "cbase.h"
#include "baseentity.h"
#include "triggers.h"
#include "modelentities.h"
#include "saverestore_utlvector.h"
#include "player_pickup.h"
#include "vphysics/friction.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define TRIGGER_DISABLED_THINK "PortalDisabledThink"
ConVar portal_debug( "portal_debug", "0", FCVAR_CHEAT, "Turn on debugging for portal connections." );
//////////////////////////////////////////////////////////////////////////
// CTriggerPortal
// Moves touched entity to a target location, changing the model's orientation
// to match the exit target. It differs from CTriggerTeleport in that it
// reorients physics and has inputs to enable/disable its function.
//////////////////////////////////////////////////////////////////////////
class CTriggerPortal : public CBaseTrigger
{
public:
DECLARE_DATADESC();
DECLARE_CLASS( CTriggerPortal, CBaseTrigger );
DECLARE_SERVERCLASS();
virtual void Spawn( void );
virtual void Activate();
void Touch( CBaseEntity *pOther );
void EndTouch(CBaseEntity *pOther);
void DisableForIncomingEntity( CBaseEntity *pEntity );
bool IsTouchingPortal( CBaseEntity *pEntity );
void DisabledThink( void );
// TEMP: Since brushes have no directionality, give this wall a forward face specified in hammer
QAngle m_qFaceAngles;
private:
string_t m_strRemotePortal;
CNetworkHandle( CTriggerPortal, m_hRemotePortal );
CUtlVector<EHANDLE> m_hDisabledForEntities;
// Input for setting remote portal entity (for teleporting to it)
void SetRemotePortal ( const char* strRemotePortalName );
void InputSetRemotePortal ( inputdata_t &inputdata );
};
LINK_ENTITY_TO_CLASS( trigger_portal, CTriggerPortal );
BEGIN_DATADESC( CTriggerPortal )
DEFINE_KEYFIELD( m_strRemotePortal, FIELD_STRING, "RemotePortal" ),
DEFINE_FIELD( m_hRemotePortal, FIELD_EHANDLE ),
DEFINE_UTLVECTOR( m_hDisabledForEntities, FIELD_EHANDLE ),
// TEMP: Only keep this field while portals are still brushes
DEFINE_FIELD( m_qFaceAngles, FIELD_VECTOR ),
DEFINE_THINKFUNC( DisabledThink ),
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
DEFINE_INPUTFUNC( FIELD_STRING, "SetRemotePortal", InputSetRemotePortal ),
END_DATADESC()
IMPLEMENT_SERVERCLASS_ST( CTriggerPortal, DT_TriggerPortal )
SendPropEHandle(SENDINFO(m_hRemotePortal)),
END_SEND_TABLE()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTriggerPortal::Spawn( void )
{
BaseClass::Spawn();
InitTrigger();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : -
//-----------------------------------------------------------------------------
void CTriggerPortal::Activate()
{
BaseClass::Activate();
m_qFaceAngles = this->GetAbsAngles();
// keep the remote portal's pointer at activate time to avoid redundant FindEntity calls
if ( m_strRemotePortal != NULL_STRING )
{
SetRemotePortal( STRING(m_strRemotePortal) );
m_strRemotePortal = NULL_STRING;
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : &inputdata -
//-----------------------------------------------------------------------------
void CTriggerPortal::InputSetRemotePortal(inputdata_t &inputdata )
{
SetRemotePortal( inputdata.value.String() );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : strRemotePortalName -
//-----------------------------------------------------------------------------
void CTriggerPortal::SetRemotePortal(const char *strRemotePortalName )
{
m_hRemotePortal = dynamic_cast<CTriggerPortal*> (gEntList.FindEntityByName( NULL, strRemotePortalName, NULL, NULL, NULL ));
if ( m_hRemotePortal == NULL )
{
Warning ( "trigger_portal: Cannot find remote portal entity named %s\n", strRemotePortalName );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pOther -
//-----------------------------------------------------------------------------
void CTriggerPortal::EndTouch(CBaseEntity *pOther)
{
BaseClass::EndTouch(pOther);
if ( portal_debug.GetBool() )
{
Msg("%s ENDTOUCH: for %s\n", GetDebugName(), pOther->GetDebugName() );
}
EHANDLE hHandle;
hHandle = pOther;
m_hDisabledForEntities.FindAndRemove( hHandle );
}
//-----------------------------------------------------------------------------
// Purpose: Upon touching a non-filtered entity, CTriggerPortal teleports them to it's
// remote portal location.
// Input : *pOther -
//-----------------------------------------------------------------------------
void CTriggerPortal::Touch( CBaseEntity *pOther )
{
// If we are enabled, and allowed to react to the touched entity
if ( PassesTriggerFilters(pOther) )
{
// If we somehow lost our pointer to the remote portal, get a new one
if ( m_hRemotePortal == NULL )
{
Disable();
return;
}
bool bDebug = portal_debug.GetBool();
if ( bDebug )
{
Msg("%s TOUCH: for %s\n", GetDebugName(), pOther->GetDebugName() );
}
// Don't touch entities that came through us and haven't left us yet.
EHANDLE hHandle;
hHandle = pOther;
if ( m_hDisabledForEntities.Find(hHandle) != m_hDisabledForEntities.InvalidIndex() )
{
Msg(" IGNORED\n", GetDebugName(), pOther->GetDebugName() );
return;
}
Pickup_ForcePlayerToDropThisObject( pOther );
// de-ground this entity
pOther->SetGroundEntity( NULL );
// Build a this --> remote transformation
VMatrix matMyModelToWorld, matMyInverse;
matMyModelToWorld = this->EntityToWorldTransform();
MatrixInverseGeneral ( matMyModelToWorld, matMyInverse );
// Teleport our object
VMatrix matRemotePortalTransform = m_hRemotePortal->EntityToWorldTransform();
Vector ptNewOrigin, vLook, vRight, vUp, vNewLook;
pOther->GetVectors( &vLook, &vRight, &vUp );
// Move origin
ptNewOrigin = matMyInverse * pOther->GetAbsOrigin();
ptNewOrigin = matRemotePortalTransform * Vector( ptNewOrigin.x, -ptNewOrigin.y, ptNewOrigin.z );
// Re-aim camera
vNewLook = matMyInverse.ApplyRotation( vLook );
vNewLook = matRemotePortalTransform.ApplyRotation( Vector( -vNewLook.x, -vNewLook.y, vNewLook.z ) );
// Reorient the physics
Vector vVelocity, vOldVelocity;
pOther->GetVelocity( &vOldVelocity );
vVelocity = matMyInverse.ApplyRotation( vOldVelocity );
vVelocity = matRemotePortalTransform.ApplyRotation( Vector( -vVelocity.x, -vVelocity.y, vVelocity.z ) );
QAngle qNewAngles;
VectorAngles( vNewLook, qNewAngles );
if ( pOther->IsPlayer() )
{
((CBasePlayer*)pOther)->SnapEyeAngles(qNewAngles);
}
Vector vecOldPos = pOther->WorldSpaceCenter();
if ( bDebug )
{
NDebugOverlay::Box( pOther->GetAbsOrigin(), pOther->WorldAlignMins(), pOther->WorldAlignMaxs(), 255,0,0, 8, 20 );
NDebugOverlay::Axis( pOther->GetAbsOrigin(), pOther->GetAbsAngles(), 10.0f, true, 50 );
}
// place player at the new destination
CTriggerPortal *pPortal = m_hRemotePortal.Get();
pPortal->DisableForIncomingEntity( pOther );
pOther->Teleport( &ptNewOrigin, &qNewAngles, &vVelocity );
if ( bDebug )
{
NDebugOverlay::Box( pOther->GetAbsOrigin(), pOther->WorldAlignMins(), pOther->WorldAlignMaxs(), 0,255,0, 8, 20 );
NDebugOverlay::Line( vecOldPos, pOther->WorldSpaceCenter(), 0,255,0, true, 20 );
NDebugOverlay::Axis( pOther->GetAbsOrigin(), pOther->GetAbsAngles(), 10.0f, true, 50 );
Msg("%s TELEPORTED: %s\n", GetDebugName(), pOther->GetDebugName() );
}
// test collision on the new teleport location
Vector vMin, vMax, vCenter;
pOther->CollisionProp()->WorldSpaceAABB( &vMin, &vMax );
vCenter = (vMin + vMax) * 0.5f;
vMin -= vCenter;
vMax -= vCenter;
Vector vStart, vEnd;
vStart = ptNewOrigin;
vEnd = ptNewOrigin;
Ray_t ray;
ray.Init( vStart, vEnd, vMin, vMax );
trace_t tr;
pPortal->TestCollision( ray, pOther->PhysicsSolidMaskForEntity(), tr );
// Teleportation caused us to hit something, deal with it.
if ( tr.DidHit() )
{
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTriggerPortal::DisableForIncomingEntity( CBaseEntity *pEntity )
{
EHANDLE hHandle;
hHandle = pEntity;
Assert( m_hDisabledForEntities.Find(hHandle) == m_hDisabledForEntities.InvalidIndex() );
m_hDisabledForEntities.AddToTail( hHandle );
// Start thinking, and remove the other as soon as it's not touching me.
// Needs to be done in addition to EndTouch, because entities may move fast
// enough through the portal to come out not touching the other portal.
SetContextThink( DisabledThink, gpGlobals->curtime + 0.1, TRIGGER_DISABLED_THINK );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CTriggerPortal::DisabledThink( void )
{
// If we've got no disabled entities left, we're done
if ( !m_hDisabledForEntities.Count() )
{
SetContextThink( NULL, gpGlobals->curtime, TRIGGER_DISABLED_THINK );
return;
}
for ( int i = m_hDisabledForEntities.Count()-1; i >= 0; i-- )
{
CBaseEntity *pEntity = m_hDisabledForEntities[i];
if ( !pEntity || !IsTouchingPortal(pEntity) )
{
m_hDisabledForEntities.Remove(i);
}
}
SetContextThink( DisabledThink, gpGlobals->curtime + 0.1, TRIGGER_DISABLED_THINK );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CTriggerPortal::IsTouchingPortal( CBaseEntity *pEntity )
{
// First, check the touchlinks. This will find non-vphysics entities touching us
touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
if ( root )
{
for ( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
{
CBaseEntity *pTouch = link->entityTouched;
if ( pTouch == pEntity )
return true;
}
}
// Then check the friction snapshot. This will find vphysics objects touching us.
IPhysicsObject *pPhysics = VPhysicsGetObject();
if ( !pPhysics )
return false;
IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
bool bFound = false;
while ( pSnapshot->IsValid() )
{
IPhysicsObject *pOther = pSnapshot->GetObject( 1 );
if ( ((CBaseEntity *)pOther->GetGameData()) == pEntity )
{
bFound = true;
break;
}
pSnapshot->NextFrictionData();
}
pPhysics->DestroyFrictionSnapshot( pSnapshot );
return bFound;
}