This commit is contained in:
nephacks
2025-06-04 03:22:50 +02:00
parent f234f23848
commit f12416cffd
14243 changed files with 6446499 additions and 26 deletions

View File

@@ -0,0 +1,280 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Instead of cloning all physics objects in a level to get proper
// near-portal reactions, only clone from a larger area near portals.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "PhysicsCloneArea.h"
#include "portal_base2d.h"
#include "collisionutils.h"
#include "env_debughistory.h"
LINK_ENTITY_TO_CLASS( physicsclonearea, CPhysicsCloneArea );
const float CPhysicsCloneArea::s_fPhysicsCloneAreaScale = 4.0f;
//#define PHYSICSCLONEAREASCALE 4.0f
/*const Vector CPhysicsCloneArea::vLocalMins( 3.0f,
-PORTAL_HALF_WIDTH * PHYSICSCLONEAREASCALE,
-PORTAL_HALF_HEIGHT * PHYSICSCLONEAREASCALE );
const Vector CPhysicsCloneArea::vLocalMaxs( PORTAL_HALF_HEIGHT * PHYSICSCLONEAREASCALE, //x is the forward which is fairly thin for portals, replacing with halfheight
PORTAL_HALF_WIDTH * PHYSICSCLONEAREASCALE,
PORTAL_HALF_HEIGHT * PHYSICSCLONEAREASCALE );*/
extern ConVar sv_portal_debug_touch;
void CPhysicsCloneArea::StartTouch( CBaseEntity *pOther )
{
if( !m_bActive )
return;
if( sv_portal_debug_touch.GetBool() )
{
DevMsg( "PortalCloneArea %i Start Touch: %s : %f\n", ((m_pAttachedPortal->m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime );
}
#if !defined( DISABLE_DEBUG_HISTORY )
if ( !IsMarkedForDeletion() )
{
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "PortalCloneArea %i Start Touch: %s : %f\n", ((m_pAttachedPortal->m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime ) );
}
#endif
m_pAttachedSimulator->StartCloningEntityFromMain( pOther );
}
void CPhysicsCloneArea::Touch( CBaseEntity *pOther )
{
if( !m_bActive )
return;
//TODO: Planar checks to see if it's a better idea to reclone/unclone
}
void CPhysicsCloneArea::EndTouch( CBaseEntity *pOther )
{
if( !m_bActive )
return;
if( sv_portal_debug_touch.GetBool() )
{
DevMsg( "PortalCloneArea %i End Touch: %s : %f\n", ((m_pAttachedPortal->m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime );
}
#if !defined( DISABLE_DEBUG_HISTORY )
if ( !IsMarkedForDeletion() )
{
ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "PortalCloneArea %i End Touch: %s : %f\n", ((m_pAttachedPortal->m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime ) );
}
#endif
m_pAttachedSimulator->StopCloningEntityFromMain( pOther );
}
void CPhysicsCloneArea::Spawn( void )
{
BaseClass::Spawn();
Assert( m_pAttachedPortal );
AddEffects( EF_NORECEIVESHADOW | EF_NOSHADOW | EF_NODRAW );
SetSolid( SOLID_OBB );
SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID );
SetMoveType( MOVETYPE_NONE );
SetCollisionGroup( COLLISION_GROUP_PLAYER );
m_fHalfWidth = m_pAttachedPortal->GetHalfWidth() * s_fPhysicsCloneAreaScale;
m_fHalfHeight = m_pAttachedPortal->GetHalfHeight() * s_fPhysicsCloneAreaScale;
m_fHalfDepth = MAX( m_fHalfWidth, m_fHalfHeight );
SetSize( GetLocalMins(), GetLocalMaxs() );
}
void CPhysicsCloneArea::Activate( void )
{
BaseClass::Activate();
}
int CPhysicsCloneArea::ObjectCaps( void )
{
return BaseClass::ObjectCaps() | FCAP_DONT_SAVE; //don't save this entity in any way, we naively recreate them
}
void CPhysicsCloneArea::UpdatePosition( void )
{
Assert( m_pAttachedPortal );
//untouch everything we're touching
touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
if( root )
{
//don't want to risk list corruption while untouching
CUtlVector<CBaseEntity *> TouchingEnts;
for( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
TouchingEnts.AddToTail( link->entityTouched );
for( int i = TouchingEnts.Count(); --i >= 0; )
{
CBaseEntity *pTouch = TouchingEnts[i];
pTouch->PhysicsNotifyOtherOfUntouch( pTouch, this );
PhysicsNotifyOtherOfUntouch( this, pTouch );
}
}
//update size as well
m_fHalfWidth = m_pAttachedPortal->GetHalfWidth() * s_fPhysicsCloneAreaScale;
m_fHalfHeight = m_pAttachedPortal->GetHalfHeight() * s_fPhysicsCloneAreaScale;
m_fHalfDepth = MAX( m_fHalfWidth, m_fHalfHeight );
SetSize( GetLocalMins(), GetLocalMaxs() );
SetAbsOrigin( m_pAttachedPortal->GetAbsOrigin() );
SetAbsAngles( m_pAttachedPortal->GetAbsAngles() );
m_bActive = m_pAttachedPortal->IsActive();
//NDebugOverlay::EntityBounds( this, 0, 0, 255, 25, 5.0f );
//RemoveFlag( FL_DONTTOUCH );
CloneNearbyEntities(); //wake new objects so they can figure out that they touch
}
void CPhysicsCloneArea::CloneNearbyEntities( 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;
}
/*{
Vector ptAABBCenter = (vAABBMins + vAABBMaxs) * 0.5f;
Vector vAABBExtent = (vAABBMaxs - vAABBMins) * 0.5f;
NDebugOverlay::Box( ptAABBCenter, -vAABBExtent, vAABBExtent, 0, 0, 255, 128, 10.0f );
}*/
int count = UTIL_EntitiesInBox( pList, 1024, vAABBMins, vAABBMaxs, 0 );
trace_t tr;
UTIL_ClearTrace( tr );
//Iterate over all the possible targets
for ( int i = 0; i < count; i++ )
{
CBaseEntity *pEntity = pList[i];
if ( pEntity && (pEntity != this) )
{
IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
if( pPhysicsObject )
{
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() ) )
{
tr.endpos = (ptOrigin + ptEntityCenter) * 0.5;
PhysicsMarkEntitiesAsTouching( pEntity, tr );
//StartTouch( pEntity );
//pEntity->WakeRestingObjects();
//pPhysicsObject->Wake();
}
}
}
}
}
void CPhysicsCloneArea::CloneTouchingEntities( void )
{
if( m_pAttachedPortal && m_pAttachedPortal->IsActive() )
{
touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
if( root )
{
for( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
m_pAttachedSimulator->StartCloningEntityFromMain( link->entityTouched );
}
}
}
CPhysicsCloneArea *CPhysicsCloneArea::CreatePhysicsCloneArea( CPortal_Base2D *pFollowPortal )
{
if( !pFollowPortal )
return NULL;
CPhysicsCloneArea *pCloneArea = (CPhysicsCloneArea *)CreateEntityByName( "physicsclonearea" );
pCloneArea->m_pAttachedPortal = pFollowPortal;
pCloneArea->m_pAttachedSimulator = &pFollowPortal->m_PortalSimulator;
DispatchSpawn( pCloneArea );
pCloneArea->UpdatePosition();
return pCloneArea;
}
void CPhysicsCloneArea::Resize( float fPortalHalfWidth, float fPortalHalfHeight )
{
fPortalHalfWidth *= s_fPhysicsCloneAreaScale;
fPortalHalfHeight *= s_fPhysicsCloneAreaScale;
if( (fPortalHalfWidth == m_fHalfWidth) && (fPortalHalfHeight == m_fHalfHeight) )
return;
m_fHalfWidth = fPortalHalfWidth;
m_fHalfHeight = fPortalHalfHeight;
m_fHalfDepth = MAX( m_fHalfWidth, m_fHalfHeight );
SetSize( GetLocalMins(), GetLocalMaxs() );
UpdatePosition();
}

View File

@@ -0,0 +1,60 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef PHYSICSCLONEAREA_H
#define PHYSICSCLONEAREA_H
#ifdef _WIN32
#pragma once
#endif
#include "baseentity.h"
class CPortal_Base2D;
class CPortalSimulator;
class CPhysicsCloneArea : public CBaseEntity
{
public:
DECLARE_CLASS( CPhysicsCloneArea, CBaseEntity );
//static const Vector vLocalMins;
//static const Vector vLocalMaxs;
virtual void StartTouch( CBaseEntity *pOther );
virtual void Touch( CBaseEntity *pOther );
virtual void EndTouch( CBaseEntity *pOther );
virtual void Spawn( void );
virtual void Activate( void );
virtual int ObjectCaps( void );
void UpdatePosition( void );
void CloneTouchingEntities( void );
void CloneNearbyEntities( void );
static CPhysicsCloneArea *CreatePhysicsCloneArea( CPortal_Base2D *pFollowPortal );
inline Vector GetLocalMins( void ) const { return Vector( 3.0f, -m_fHalfWidth, -m_fHalfHeight ); }
inline Vector GetLocalMaxs( void ) const { return Vector( m_fHalfDepth, m_fHalfWidth, m_fHalfHeight ); }
void Resize( float fPortalHalfWidth, float fPortalHalfHeight );
private:
CPortal_Base2D *m_pAttachedPortal;
CPortalSimulator *m_pAttachedSimulator;
bool m_bActive;
float m_fHalfWidth, m_fHalfHeight, m_fHalfDepth;
static const float s_fPhysicsCloneAreaScale;
};
#endif //#ifndef PHYSICSCLONEAREA_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: Clones a physics object by use of shadows
//
// $NoKeywords: $
//=============================================================================//
#ifndef PHYSICSSHADOWCLONE_H
#define PHYSICSSHADOWCLONE_H
#ifdef _WIN32
#pragma once
#endif
#include "vphysics_interface.h"
#include "BaseEntity.h"
#include "baseanimating.h"
class CPhysicsShadowClone;
struct PhysicsObjectCloneLink_t
{
IPhysicsObject *pSource;
IPhysicsShadowController *pShadowController;
IPhysicsObject *pClone;
};
struct CPhysicsShadowCloneLL
{
CPhysicsShadowClone *pClone;
CPhysicsShadowCloneLL *pNext;
};
#define FVPHYSICS_IS_SHADOWCLONE 0x4000
class CPhysicsShadowClone : public CBaseAnimating
{
DECLARE_CLASS( CPhysicsShadowClone, CBaseAnimating );
private:
EHANDLE m_hClonedEntity; //the entity we're supposed to be cloning the physics of
VMatrix m_matrixShadowTransform; //all cloned coordinates and angles will be run through this matrix before being applied
VMatrix m_matrixShadowTransform_Inverse;
CUtlVector<PhysicsObjectCloneLink_t> m_CloneLinks; //keeps track of which of our physics objects are linked to the source's objects
bool m_bShadowTransformIsIdentity; //the shadow transform doesn't update often, so we can cache this
bool m_bImmovable; //cloning a track train or door, something that doesn't really work on a force-based level
bool m_bInAssumedSyncState;
void FullSyncClonedPhysicsObjects( bool bTeleport );
void SyncEntity( bool bPullChanges );
IPhysicsEnvironment *m_pOwnerPhysEnvironment; //clones exist because of multi-environment situations
public:
CPhysicsShadowClone( void );
virtual ~CPhysicsShadowClone( void );
bool m_bShouldUpSync;
DBG_CODE_NOSCOPE( const char *m_szDebugMarker; );
//do the thing with the stuff, you know, the one that goes WooooWooooWooooWooooWoooo
virtual void Spawn( void );
//crush, kill, DESTROY!!!!!
void Free( void );
//syncs to the source entity in every way possible, assumed sync does some rudimentary tests to see if the object is in sync, and if so, skips the update
void FullSync( bool bAllowAssumedSync = false );
//syncs just the physics objects, bPullChanges should be true when this clone should match it's source, false when it should force differences onto the source entity
void PartialSync( bool bPullChanges );
//virtual bool CreateVPhysics( void );
virtual void VPhysicsDestroyObject( void );
virtual int VPhysicsGetObjectList( IPhysicsObject **pList, int listMax );
virtual int ObjectCaps( void );
virtual void UpdateOnRemove( void );
//routing to the source entity for cloning goodness
virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const;
//avoid blocking traces that are supposed to hit our source entity
virtual bool TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr );
//is this clone occupying the exact same space as the object it's cloning?
inline bool IsUntransformedClone( void ) const { return m_bShadowTransformIsIdentity; };
void SetCloneTransformationMatrix( const matrix3x4_t &matTransform );
inline bool IsInAssumedSyncState( void ) const { return m_bInAssumedSyncState; }
inline IPhysicsEnvironment *GetOwnerEnvironment( void ) const { return m_pOwnerPhysEnvironment; }
//what entity are we cloning?
void SetClonedEntity( EHANDLE hEntToClone );
EHANDLE GetClonedEntity( void );
virtual void VPhysicsCollision( int index, gamevcollisionevent_t *pEvent );
//damage relays to source entity if anything ever hits the clone
virtual bool PassesDamageFilter( const CTakeDamageInfo &info );
virtual bool CanBeHitByMeleeAttack( CBaseEntity *pAttacker );
virtual int OnTakeDamage( const CTakeDamageInfo &info );
virtual int TakeHealth( float flHealth, int bitsDamageType );
virtual void Event_Killed( const CTakeDamageInfo &info );
static CPhysicsShadowClone *CreateShadowClone( IPhysicsEnvironment *pInPhysicsEnvironment, EHANDLE hEntToClone, const char *szDebugMarker, const matrix3x4_t *pTransformationMatrix = NULL );
//given a physics object that is part of this clone, tells you which physics object in the source
IPhysicsObject *TranslatePhysicsToClonedEnt( const IPhysicsObject *pPhysics );
static bool IsShadowClone( const CBaseEntity *pEntity );
static CPhysicsShadowCloneLL *GetClonesOfEntity( const CBaseEntity *pEntity );
static void FullSyncAllClones( void );
static CUtlVector<CPhysicsShadowClone *> const &g_ShadowCloneList;
friend void DrawDebugOverlayForShadowClone( CPhysicsShadowClone *pClone );
//only really necessary to call for entities that create custom collideables.
static void NotifyDestroy( IPhysicsObject *pDestroyingPhys, CBaseEntity *pOwningEntity = NULL ); //passing in the original owner entity just makes the search faster
static void NotifyDestroy( CPhysCollide *pDestroyingCollide, CBaseEntity *pOwningEntity = NULL ); //passing in the original owner entity just makes the search faster
private:
void DestroyClonedPhys( IPhysicsObject *pPhys );
void DestroyClonedCollideable( CPhysCollide *pCollide );
};
class CTraceFilterTranslateClones : public CTraceFilter //give it another filter, and it'll translate shadow clones into their source entity for tests
{
ITraceFilter *m_pActualFilter; //the filter that tests should be forwarded to after translating clones
public:
CTraceFilterTranslateClones( ITraceFilter *pOtherFilter ) : m_pActualFilter(pOtherFilter) {};
virtual bool ShouldHitEntity( IHandleEntity *pEntity, int contentsMask );
virtual TraceType_t GetTraceType() const;
};
#endif //#ifndef PHYSICSSHADOWCLONE_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,231 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#ifndef PORTAL_BASE2D_H
#define PORTAL_BASE2D_H
#ifdef _WIN32
#pragma once
#endif
#include "baseanimating.h"
#include "PortalSimulation.h"
#include "pvs_extender.h"
#include "portal_base2d_shared.h"
// FIX ME
#include "portal_shareddefs.h"
class CPhysicsCloneArea;
class CPortal_Base2D : public CBaseAnimating, public CPortalSimulatorEventCallbacks, public CPVS_Extender, public CPortal_Base2D_Shared
{
public:
DECLARE_CLASS( CPortal_Base2D, CBaseAnimating );
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
CPortal_Base2D( void );
virtual ~CPortal_Base2D( void );
CNetworkHandle( CPortal_Base2D, m_hLinkedPortal ); //the portal this portal is linked to
VMatrix m_matrixThisToLinked; //the matrix that will transform a point relative to this portal, to a point relative to the linked portal
CNetworkVar( bool, m_bIsPortal2 ); //For teleportation, this doesn't matter, but for drawing and moving, it matters
Vector m_vPrevForward; //used for the indecisive push in find closest passable spaces when portal is moved
bool m_bSharedEnvironmentConfiguration; //this will be set by an instance of CPortal_Environment when two environments are in close proximity
EHANDLE m_hMicrophone; //the microphone for teleporting sound
EHANDLE m_hSpeaker; //the speaker for teleported sound
bool m_bMicAndSpeakersLinkedToRemote;
Vector m_vAudioOrigin;
Vector m_vDelayedPosition;
QAngle m_qDelayedAngles;
int m_iDelayedFailure;
Vector m_vOldPosition;
QAngle m_qOldAngles;
EHANDLE m_hPlacedBy;
int m_nPortalColor;
COutputEvent m_OnPlacedSuccessfully; // Output in hammer for when this portal was successfully placed (not attempted and fizzed).
COutputEvent m_OnEntityTeleportFromMe;
COutputEvent m_OnPlayerTeleportFromMe;
COutputEvent m_OnEntityTeleportToMe;
COutputEvent m_OnPlayerTeleportToMe;
CNetworkVector( m_ptOrigin );
Vector m_vForward, m_vUp, m_vRight;
CNetworkQAngle( m_qAbsAngle );
cplane_t m_plane_Origin; //a portal plane on the entity origin
CPhysicsCloneArea *m_pAttachedCloningArea;
bool IsPortal2() const;
void SetIsPortal2( bool bIsPortal2 );
const VMatrix& MatrixThisToLinked() const;
virtual int UpdateTransmitState( void ) // set transmit filter to transmit always
{
return SetTransmitState( FL_EDICT_ALWAYS );
}
virtual void Spawn( void );
virtual void Activate( void );
virtual void OnRestore( void );
virtual bool IsActive( void ) const { return m_bActivated; }
virtual bool GetOldActiveState( void ) const { return m_bOldActivatedState; }
virtual void SetActive( bool bActive );
virtual void UpdateOnRemove( void );
void TestRestingSurfaceThink( void );
static const char * s_szTestRestingSurfaceThinkContext;
void DeactivatePortalOnThink( void );
void DeactivatePortalNow( void );
static const char * s_szDeactivatePortalNowContext;
virtual void OnPortalDeactivated( void );
bool IsActivedAndLinked( void ) const;
bool IsFloorPortal( float fThreshold = 0.8f ) const;
bool IsCeilingPortal( float fThreshold = -0.8f ) const;
void WakeNearbyEntities( void ); //wakes all nearby entities in-case there's been a significant change in how they can rest near a portal
void ForceEntityToFitInPortalWall( CBaseEntity *pEntity ); //projects an object's center into the middle of the portal wall hall, and traces back to where it wants to be
virtual void NewLocation( const Vector &vOrigin, const QAngle &qAngles );
void PunchPenetratingPlayer( CBasePlayer *pPlayer ); // adds outward force to player intersecting the portal plane
void PunchAllPenetratingPlayers( void ); // adds outward force to player intersecting the portal plane
virtual void StartTouch( CBaseEntity *pOther );
virtual void Touch( CBaseEntity *pOther );
virtual void EndTouch( CBaseEntity *pOther );
bool ShouldTeleportTouchingEntity( CBaseEntity *pOther ); //assuming the entity is or was just touching the portal, check for teleportation conditions
void TeleportTouchingEntity( CBaseEntity *pOther );
virtual void PreTeleportTouchingEntity( CBaseEntity *pOther ) {};
virtual void PostTeleportTouchingEntity( CBaseEntity *pOther ) {};
virtual void PhysicsSimulate( void );
virtual void UpdatePortalLinkage( void );
void UpdatePortalTeleportMatrix( void ); //computes the transformation from this portal to the linked portal, and will update the remote matrix as well
//void SendInteractionMessage( CBaseEntity *pEntity, bool bEntering ); //informs clients that the entity is interacting with a portal (mostly used for clip planes)
bool SharedEnvironmentCheck( CBaseEntity *pEntity ); //does all testing to verify that the object is better handled with this portal instead of the other
// The four corners of the portal in worldspace, updated on placement. The four points will be coplanar on the portal plane.
Vector m_vPortalCorners[4];
CNetworkVarEmbedded( CPortalSimulator, m_PortalSimulator );
//virtual bool CreateVPhysics( void );
//virtual void VPhysicsDestroyObject( void );
virtual bool TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr );
virtual void PortalSimulator_TookOwnershipOfEntity( CBaseEntity *pEntity );
virtual void PortalSimulator_ReleasedOwnershipOfEntity( CBaseEntity *pEntity );
virtual void CreateMicAndSpeaker( void );
// Add or remove listeners
void AddPortalEventListener( EHANDLE hListener );
void RemovePortalEventListener( EHANDLE hListener );
void OnEntityTeleportedToPortal( CBaseEntity *pEntity );
void OnEntityTeleportedFromPortal( CBaseEntity *pEntity );
protected:
CNetworkVar( bool, m_bActivated ); //a portal can exist and not be active
CNetworkVar( bool, m_bOldActivatedState ); //the old state
void BroadcastPortalEvent( PortalEvent_t nEventType );
CUtlVector<EHANDLE> m_PortalEventListeners; // Collection of entities (by handle) who wish to receive notification of portal events (fizzle, moved, etc)
void RemovePortalMicAndSpeaker(); // Cleans up the portal's internal audio members
void UpdateCorners( void ); // Updates the four corners of this portal on spawn and placement
void UpdateClientCheckPVS( void ); // Tells clients to update the cached PVS in g_ClientCheck
void UpdateCollisionShape( void );
CNetworkVar( float, m_fNetworkHalfWidth );
CNetworkVar( float, m_fNetworkHalfHeight );
CNetworkVar( bool, m_bIsMobile ); //is this portal currently making small movements along with whatever brush it's attached to? Portal physics are disabled while nudging and resume when stabilized
CPhysCollide *m_pCollisionShape;
public:
CPortal_Base2D *GetLinkedPortal( void ) { return m_hLinkedPortal; }
inline float GetHalfWidth( void ) const { return m_fNetworkHalfWidth; }
inline float GetHalfHeight( void ) const { return m_fNetworkHalfHeight; }
inline Vector GetLocalMins( void ) const { return Vector( 0.0f, -m_fNetworkHalfWidth, -m_fNetworkHalfHeight ); }
inline Vector GetLocalMaxs( void ) const { return Vector( 64.0f, m_fNetworkHalfWidth, m_fNetworkHalfHeight ); }
//inline void SetHalfSizes( float fHalfWidth, float fHalfHeight ) { m_fHalfWidth = fHalfWidth; m_fHalfHeight = fHalfHeight; }
inline bool IsMobile( void ) const { return m_bIsMobile; }
void SetMobileState( bool bSet );
void Resize( float fHalfWidth, float fHalfHeight );
virtual CServerNetworkProperty *GetExtenderNetworkProp( void );
virtual const edict_t *GetExtenderEdict( void ) const;
virtual Vector GetExtensionPVSOrigin( void );
virtual bool IsExtenderValid( void );
//to whittle down views through recursive portals, we clip the portal's planar polygon by a frustum, then fit a new (smaller) frustum to that polygon. These two let you specify the polygon we clip and fit to
virtual int GetPolyVertCount( void );
virtual int ComputeFrustumThroughPolygon( const Vector &vVisOrigin, const VPlane *pInputFrustum, int iInputFrustumPlanes, VPlane *pOutputFrustum, int iOutputFrustumMaxPlanes );
//This portal is decidedly visible, recursively extend the visibility problem
virtual void 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 );
//it shouldn't matter, but the convention should be that we query the exit portal for these values
virtual float GetMinimumExitSpeed( bool bPlayer, bool bEntranceOnFloor, bool bExitOnFloor, const Vector &vEntityCenterAtExit, CBaseEntity *pEntity ); //return -FLT_MAX for no minimum
virtual float GetMaximumExitSpeed( bool bPlayer, bool bEntranceOnFloor, bool bExitOnFloor, const Vector &vEntityCenterAtExit, CBaseEntity *pEntity ); //return FLT_MAX for no maximum
//does all the gruntwork of figuring out flooriness and calling the two above
static void GetExitSpeedRange( CPortal_Base2D *pEntrancePortal, bool bPlayer, float &fExitMinimum, float &fExitMaximum, const Vector &vEntityCenterAtExit, CBaseEntity *pEntity );
private:
Vector m_vPortalSpawnLocation; // use this position to check against portal->AbsOrigin of the portal to see if it's moving too far (moving portal is very bad)
};
//-----------------------------------------------------------------------------
// inline state querying methods
//-----------------------------------------------------------------------------
inline bool CPortal_Base2D::IsPortal2() const
{
return m_bIsPortal2;
}
inline void CPortal_Base2D::SetIsPortal2( bool bIsPortal2 )
{
m_bIsPortal2 = bIsPortal2;
}
inline const VMatrix& CPortal_Base2D::MatrixThisToLinked() const
{
return m_matrixThisToLinked;
}
void AddPortalVisibilityToPVS( CPortal_Base2D* pPortal, int outputpvslength, unsigned char *outputpvs );
void EntityPortalled( CPortal_Base2D *pPortal, CBaseEntity *pOther, const Vector &vNewOrigin, const QAngle &qNewAngles, bool bForcedDuck );
extern ConVar sv_allow_mobile_portals;
#endif //#ifndef PORTAL_BASE2D_H

View File

@@ -0,0 +1,543 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "portal_physics_collisionevent.h"
#include "physicsshadowclone.h"
#include "prop_combine_ball.h"
#include "portal_player.h"
#include "portal/weapon_physcannon.h" //grab controller
#define DEBUG_COLLISION_RULES 0
#if (DEBUG_COLLISION_RULES == 1)
ConVar sv_watchcollision_1( "sv_watchcollision_1", "-1" );
ConVar sv_watchcollision_2( "sv_watchcollision_2", "-1" );
#endif
int CPortal_CollisionEvent::ShouldCollide( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1 )
{
if ( !pGameData0 || !pGameData1 )
return 1;
#if (DEBUG_COLLISION_RULES == 1)
bool bBreak = false;
if( ((((CBaseEntity *)pGameData0)->entindex() == sv_watchcollision_1.GetInt()) || (((CBaseEntity *)pGameData0)->entindex() == sv_watchcollision_2.GetInt())) &&
((((CBaseEntity *)pGameData1)->entindex() == sv_watchcollision_1.GetInt()) || (((CBaseEntity *)pGameData1)->entindex() == sv_watchcollision_2.GetInt())) )
{
bBreak = true;
}
#endif
AssertOnce( pObj0 && pObj1 );
uint nFlags0 = pObj0->GetGameFlags();
uint nFlags1 = pObj1->GetGameFlags();
uint nAllFlags = nFlags0 | nFlags1;
bool bShadowClonesInvolved = ( nAllFlags & FVPHYSICS_IS_SHADOWCLONE) != 0;
if( bShadowClonesInvolved )
{
//at least one shadow clone
if( (nFlags0 & nFlags1) & FVPHYSICS_IS_SHADOWCLONE )
return 0; //both are shadow clones
if( nAllFlags & FVPHYSICS_PLAYER_HELD )
{
//at least one is held
//don't let players collide with objects they're holding, they get kinda messed up sometimes
if( pGameData0 && ((CBaseEntity *)pGameData0)->IsPlayer() && (GetPlayerHeldEntity( (CBasePlayer *)pGameData0 ) == (CBaseEntity *)pGameData1) )
return 0;
if( pGameData1 && ((CBaseEntity *)pGameData1)->IsPlayer() && (GetPlayerHeldEntity( (CBasePlayer *)pGameData1 ) == (CBaseEntity *)pGameData0) )
return 0;
}
}
//everything is in one environment. This means we must tightly control what collides with what
if( pGameData0 != pGameData1 )
{
//this code only decides what CAN'T collide due to portal environment differences, things that should collide will pass through here to deeper ShouldCollide() code
bool bStatic[2] = { pObj0->IsStatic(), pObj1->IsStatic() };
AssertOnce( (bStatic[0] && bStatic[1]) == false ); //hopefully the system doesn't even call in for this, they're both static and can't collide
if( bStatic[0] && bStatic[1] )
return 0;
CBaseEntity *pEntities[2] = { (CBaseEntity *)pGameData0, (CBaseEntity *)pGameData1 };
IPhysicsObject *pPhysObjects[2] = { pObj0, pObj1 };
CPortalSimulator *pSimulators[2];
pSimulators[0] = CPortalSimulator::GetSimulatorThatOwnsEntity( (CBaseEntity *)pGameData0 );
pSimulators[1] = CPortalSimulator::GetSimulatorThatOwnsEntity( (CBaseEntity *)pGameData1 );
#ifdef _DEBUG
for( int i = 0; i != 2; ++i )
{
if( (pSimulators[i] != NULL) && CPhysicsShadowClone::IsShadowClone( pEntities[i] ) )
{
CPhysicsShadowClone *pClone = (CPhysicsShadowClone *)pEntities[i];
CBaseEntity *pSource = pClone->GetClonedEntity();
CPortalSimulator *pSourceSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pClone )->GetLinkedPortalSimulator();
Assert( (pSimulators[i]->GetInternalData().Simulation.Dynamic.EntFlags[pClone->entindex()] & PSEF_IS_IN_PORTAL_HOLE) == (pSourceSimulator->GetInternalData().Simulation.Dynamic.EntFlags[pSource->entindex()] & PSEF_IS_IN_PORTAL_HOLE) );
}
}
#endif
if( (pSimulators[0] == pSimulators[1]) ) //same simulator
{
if( pSimulators[0] != NULL ) //and not main world
{
if( bStatic[0] || bStatic[1] )
{
for( int i = 0; i != 2; ++i )
{
if( bStatic[i] )
{
if( CPSCollisionEntity::IsPortalSimulatorCollisionEntity( pEntities[i] ) )
{
PS_PhysicsObjectSourceType_t objectSource;
if( pSimulators[i]->CreatedPhysicsObject( pPhysObjects[i], &objectSource ) &&
((objectSource == PSPOST_REMOTE_BRUSHES) || (objectSource == PSPOST_REMOTE_STATICPROPS)) )
{
if( (pSimulators[1-i]->GetInternalData().Simulation.Dynamic.EntFlags[pEntities[1-i]->entindex()] & PSEF_IS_IN_PORTAL_HOLE) == 0 )
return 0; //require that the entity be in the portal hole before colliding with transformed geometry
//FIXME: The above requirement might fail horribly for transformed collision blocking the portal from the other side and fast moving objects
}
}
break;
}
}
}
else if( bShadowClonesInvolved )
{
if( ((pSimulators[0]->GetInternalData().Simulation.Dynamic.EntFlags[pEntities[0]->entindex()] |
pSimulators[1]->GetInternalData().Simulation.Dynamic.EntFlags[pEntities[1]->entindex()]) &
PSEF_IS_IN_PORTAL_HOLE) == 0 )
{
return 0; //neither entity was actually in the portal hole
}
}
}
}
else //different simulators
{
if( bShadowClonesInvolved ) //entities can only collide with shadow clones "owned" by the same simulator.
return 0;
if( bStatic[0] || bStatic[1] )
{
for( int i = 0; i != 2; ++i )
{
if( bStatic[i] )
{
int j = 1-i;
CPortalSimulator *pSimulator_Entity = pSimulators[j];
if( pEntities[i]->IsWorld() )
{
Assert( CPortalSimulator::GetSimulatorThatCreatedPhysicsObject( pPhysObjects[i] ) == NULL );
if( pSimulator_Entity )
return 0;
}
else
{
CPortalSimulator *pSimulator_Static = CPortalSimulator::GetSimulatorThatCreatedPhysicsObject( pPhysObjects[i] ); //might have been a static prop which would yield a new simulator
if( pSimulator_Static )
{
if(pSimulator_Static != pSimulator_Entity)
return 0; //static collideable is from a different simulator
}
else if( pSimulator_Entity )
{
if( (pSimulator_Entity->GetInternalData().Simulation.Dynamic.EntFlags[pEntities[i]->entindex()] & PSEF_CLONES_ENTITY_FROM_MAIN) == 0 )
{
//entity is in a portal environment, static is not, static not cloned from main.
if( !pPhysObjects[i]->IsTrigger() ) //we should probably do this with triggers too. But it breaks tractor beams in devtest when the cube portals, too late in the ship cycle to chase the sweater thread without a good reason
return 0;
}
}
}
break;
}
}
}
else
{
Assert( CPSCollisionEntity::IsPortalSimulatorCollisionEntity( pEntities[0] ) == false );
Assert( CPSCollisionEntity::IsPortalSimulatorCollisionEntity( pEntities[1] ) == false );
for( int i = 0; i != 2; ++i )
{
if( pSimulators[i] )
{
//entities in the physics environment only collide with statics created by the environment (handled above), entities in the same environment (also above), or entities that should be cloned from main to the same environment
if( (pSimulators[i]->GetInternalData().Simulation.Dynamic.EntFlags[pEntities[1-i]->entindex()] & PSEF_CLONES_ENTITY_FROM_MAIN) == 0 ) //not cloned from main
return 0;
}
}
}
}
}
return BaseClass::ShouldCollide( pObj0, pObj1, pGameData0, pGameData1 );
}
static bool s_bPenetrationSolvingDisabled = false;
static CUtlStack<bool> s_DisablePenetatrationSolving;
void CPortal_CollisionEvent::DisablePenetrationSolving_Push( bool bDisable )
{
s_DisablePenetatrationSolving.Push( bDisable );
s_bPenetrationSolvingDisabled = bDisable;
}
void CPortal_CollisionEvent::DisablePenetrationSolving_Pop( void )
{
s_DisablePenetatrationSolving.Pop();
if( s_DisablePenetatrationSolving.Count() > 0 )
{
s_bPenetrationSolvingDisabled = s_DisablePenetatrationSolving.Top();
}
else
{
s_bPenetrationSolvingDisabled = false;
}
}
int CPortal_CollisionEvent::ShouldSolvePenetration( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1, float dt )
{
if( s_bPenetrationSolvingDisabled )
return 0;
if( (pGameData0 == NULL) || (pGameData1 == NULL) )
return 0;
// For portal, don't solve penetrations on combine balls
if( FClassnameIs( (CBaseEntity *)pGameData0, "prop_energy_ball" ) ||
FClassnameIs( (CBaseEntity *)pGameData1, "prop_energy_ball" ) )
return 0;
if( (pObj0->GetGameFlags() | pObj1->GetGameFlags()) & FVPHYSICS_PLAYER_HELD )
{
//at least one is held
CBaseEntity *pHeld;
CBaseEntity *pOther;
IPhysicsObject *pPhysHeld;
IPhysicsObject *pPhysOther;
if( pObj0->GetGameFlags() & FVPHYSICS_PLAYER_HELD )
{
pHeld = (CBaseEntity *)pGameData0;
pPhysHeld = pObj0;
pOther = (CBaseEntity *)pGameData1;
pPhysOther = pObj1;
}
else
{
pHeld = (CBaseEntity *)pGameData1;
pPhysHeld = pObj1;
pOther = (CBaseEntity *)pGameData0;
pPhysOther = pObj0;
}
//don't let players collide with objects they're holding, they get kinda messed up sometimes
if( pOther->IsPlayer() && GetPlayerHeldEntity( (CBasePlayer *)pOther ) == pHeld )
{
return 0;
}
//held objects are clipping into other objects when travelling across a portal. We're close to ship, so this seems to be the
//most localized way to make a fix.
//Note that we're not actually going to change whether it should solve, we're just going to tack on some hacks
CPortal_Player *pHoldingPlayer = (CPortal_Player *)GetPlayerHoldingEntity( pHeld );
if( !pHoldingPlayer && CPhysicsShadowClone::IsShadowClone( pHeld ) )
{
pHoldingPlayer = (CPortal_Player *)GetPlayerHoldingEntity( ((CPhysicsShadowClone *)pHeld)->GetClonedEntity() );
}
Assert( pHoldingPlayer );
if ( pHoldingPlayer && !pHoldingPlayer->IsUsingVMGrab() )
{
CGrabController *pGrabController = GetGrabControllerForPlayer( pHoldingPlayer );
if ( !pGrabController )
pGrabController = GetGrabControllerForPhysCannon( pHoldingPlayer->GetActiveWeapon() );
Assert( pGrabController );
if( pGrabController )
{
GrabController_SetPortalPenetratingEntity( pGrabController, pOther );
}
//NDebugOverlay::EntityBounds( pHeld, 0, 0, 255, 16, 1.0f );
//NDebugOverlay::EntityBounds( pOther, 255, 0, 0, 16, 1.0f );
//pPhysOther->Wake();
//FindClosestPassableSpace( pOther, Vector( 0.0f, 0.0f, 1.0f ) );
}
}
if( (pObj0->GetGameFlags() | pObj1->GetGameFlags()) & FVPHYSICS_IS_SHADOWCLONE )
{
//at least one shadowclone is involved
if( (pObj0->GetGameFlags() & pObj1->GetGameFlags()) & FVPHYSICS_IS_SHADOWCLONE ) //don't solve between two shadowclones, they're just going to resync in a frame anyways
return 0;
IPhysicsObject * const pObjects[2] = { pObj0, pObj1 };
for( int i = 0; i != 2; ++i )
{
if( pObjects[i]->GetGameFlags() & FVPHYSICS_IS_SHADOWCLONE )
{
int j = 1 - i;
if( !pObjects[j]->IsMoveable() )
return 0; //don't solve between shadow clones and statics
if( ((CPhysicsShadowClone *)(pObjects[i]->GetGameData()))->GetClonedEntity() == (pObjects[j]->GetGameData()) )
return 0; //don't solve between a shadow clone and its source entity
}
}
}
return BaseClass::ShouldSolvePenetration( pObj0, pObj1, pGameData0, pGameData1, dt );
}
// Data for energy ball vs held item mass swapping hack
static float s_fSavedMass[2];
static bool s_bChangedMass[2] = { false, false };
static bool s_bUseUnshadowed[2] = { false, false };
static IPhysicsObject *s_pUnshadowed[2] = { NULL, NULL };
static void ModifyWeight_PreCollision( vcollisionevent_t *pEvent )
{
Assert( (pEvent->pObjects[0] != NULL) && (pEvent->pObjects[1] != NULL) );
CBaseEntity *pUnshadowedEntities[2];
IPhysicsObject *pUnshadowedObjects[2];
for( int i = 0; i != 2; ++i )
{
if( pEvent->pObjects[i]->GetGameFlags() & FVPHYSICS_IS_SHADOWCLONE )
{
CPhysicsShadowClone *pClone = ((CPhysicsShadowClone *)pEvent->pObjects[i]->GetGameData());
pUnshadowedEntities[i] = pClone->GetClonedEntity();
if( pUnshadowedEntities[i] == NULL )
return;
pUnshadowedObjects[i] = pClone->TranslatePhysicsToClonedEnt( pEvent->pObjects[i] );
if( pUnshadowedObjects[i] == NULL )
return;
}
else
{
pUnshadowedEntities[i] = (CBaseEntity *)pEvent->pObjects[i]->GetGameData();
pUnshadowedObjects[i] = pEvent->pObjects[i];
}
}
// HACKHACK: Reduce mass for combine ball vs movable brushes so the collision
// appears fully elastic regardless of mass ratios
for( int i = 0; i != 2; ++i )
{
int j = 1-i;
// One is a combine ball, if the other is a movable brush, reduce the combine ball mass
if ( pUnshadowedEntities[i] != NULL && FClassnameIs( pUnshadowedEntities[j], "prop_combine_ball") )
{
if ( pUnshadowedEntities[i]->GetMoveType() == MOVETYPE_PUSH )
{
s_bChangedMass[j] = true;
s_fSavedMass[j] = pUnshadowedObjects[j]->GetMass();
pEvent->pObjects[j]->SetMass( VPHYSICS_MIN_MASS );
if( pUnshadowedObjects[j] != pEvent->pObjects[j] )
{
s_bUseUnshadowed[j] = true;
s_pUnshadowed[j] = pUnshadowedObjects[j];
pUnshadowedObjects[j]->SetMass( VPHYSICS_MIN_MASS );
}
}
//HACKHACK: last minute problem knocking over turrets with energy balls, up the mass of the ball by a lot
if( FClassnameIs( pUnshadowedEntities[i], "npc_portal_turret_floor" ) )
{
pUnshadowedObjects[j]->SetMass( pUnshadowedEntities[i]->VPhysicsGetObject()->GetMass() );
}
}
}
for( int i = 0; i != 2; ++i )
{
if( ( pUnshadowedObjects[i] && pUnshadowedObjects[i]->GetGameFlags() & FVPHYSICS_PLAYER_HELD ) )
{
int j = 1-i;
if( FClassnameIs( pUnshadowedEntities[j], "prop_combine_ball") )
{
// [j] is the combine ball, set mass low
// if the above ball vs brush entity check didn't already change the mass, change the mass
if ( !s_bChangedMass[j] )
{
s_bChangedMass[j] = true;
s_fSavedMass[j] = pUnshadowedObjects[j]->GetMass();
pEvent->pObjects[j]->SetMass( VPHYSICS_MIN_MASS );
if( pUnshadowedObjects[j] != pEvent->pObjects[j] )
{
s_bUseUnshadowed[j] = true;
s_pUnshadowed[j] = pUnshadowedObjects[j];
pUnshadowedObjects[j]->SetMass( VPHYSICS_MIN_MASS );
}
}
// [i] is the held object, set mass high
s_bChangedMass[i] = true;
s_fSavedMass[i] = pUnshadowedObjects[i]->GetMass();
pEvent->pObjects[i]->SetMass( VPHYSICS_MAX_MASS );
if( pUnshadowedObjects[i] != pEvent->pObjects[i] )
{
s_bUseUnshadowed[i] = true;
s_pUnshadowed[i] = pUnshadowedObjects[i];
pUnshadowedObjects[i]->SetMass( VPHYSICS_MAX_MASS );
}
}
else if( pEvent->pObjects[j]->GetGameFlags() & FVPHYSICS_IS_SHADOWCLONE )
{
//held object vs shadow clone, set held object mass back to grab controller saved mass
// [i] is the held object
s_bChangedMass[i] = true;
s_fSavedMass[i] = pUnshadowedObjects[i]->GetMass();
CGrabController *pGrabController = NULL;
CBaseEntity *pLookingForEntity = (CBaseEntity*)pEvent->pObjects[i]->GetGameData();
CBasePlayer *pHoldingPlayer = GetPlayerHoldingEntity( pLookingForEntity );
if( pHoldingPlayer )
pGrabController = GetGrabControllerForPlayer( pHoldingPlayer );
float fSavedMass, fSavedRotationalDamping;
AssertMsg( pGrabController, "Physics object is held, but we can't find the holding controller." );
GetSavedParamsForCarriedPhysObject( pGrabController, pUnshadowedObjects[i], &fSavedMass, &fSavedRotationalDamping );
pEvent->pObjects[i]->SetMass( fSavedMass );
if( pUnshadowedObjects[i] != pEvent->pObjects[i] )
{
s_bUseUnshadowed[i] = true;
s_pUnshadowed[i] = pUnshadowedObjects[i];
pUnshadowedObjects[i]->SetMass( fSavedMass );
}
}
}
}
}
void CPortal_CollisionEvent::PreCollision( vcollisionevent_t *pEvent )
{
ModifyWeight_PreCollision( pEvent );
return BaseClass::PreCollision( pEvent );
}
static void ModifyWeight_PostCollision( vcollisionevent_t *pEvent )
{
for( int i = 0; i != 2; ++i )
{
if( s_bChangedMass[i] )
{
pEvent->pObjects[i]->SetMass( s_fSavedMass[i] );
if( s_bUseUnshadowed[i] )
{
s_pUnshadowed[i]->SetMass( s_fSavedMass[i] );
s_bUseUnshadowed[i] = false;
}
s_bChangedMass[i] = false;
}
}
}
void CPortal_CollisionEvent::PostCollision( vcollisionevent_t *pEvent )
{
ModifyWeight_PostCollision( pEvent );
return BaseClass::PostCollision( pEvent );
}
void CPortal_CollisionEvent::PostSimulationFrame()
{
//this actually happens once per physics environment simulation, and we don't want that, so do nothing and we'll get a different version manually called
}
void CPortal_CollisionEvent::PortalPostSimulationFrame( void )
{
BaseClass::PostSimulationFrame();
}
void CPortal_CollisionEvent::AddDamageEvent( CBaseEntity *pEntity, const CTakeDamageInfo &info, IPhysicsObject *pInflictorPhysics, bool bRestoreVelocity, const Vector &savedVel, const AngularImpulse &savedAngVel )
{
const CTakeDamageInfo *pPassDownInfo = &info;
CTakeDamageInfo ReplacementDamageInfo; //only used some of the time
if( (info.GetDamageType() & DMG_CRUSH) &&
(pInflictorPhysics->GetGameFlags() & FVPHYSICS_IS_SHADOWCLONE) &&
(!info.BaseDamageIsValid()) &&
(info.GetDamageForce().LengthSqr() > (20000.0f * 20000.0f))
)
{
//VERY likely this was caused by the penetration solver. Since a shadow clone is involved we're going to ignore it becuase it causes more problems than it solves in this case
ReplacementDamageInfo = info;
ReplacementDamageInfo.SetDamage( 0.0f );
pPassDownInfo = &ReplacementDamageInfo;
}
BaseClass::AddDamageEvent( pEntity, *pPassDownInfo, pInflictorPhysics, bRestoreVelocity, savedVel, savedAngVel );
}

View File

@@ -0,0 +1,35 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#ifndef PORTAL_PHYSICS_COLLISIONEVENT_H
#define PORTAL_PHYSICS_COLLISIONEVENT_H
#ifdef _WIN32
#pragma once
#endif
#include "physics_collisionevent.h"
class CPortal_CollisionEvent : public CCollisionEvent
{
public:
DECLARE_CLASS_GAMEROOT( CPortal_CollisionEvent, CCollisionEvent );
virtual int ShouldCollide( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1 );
virtual void PreCollision( vcollisionevent_t *pEvent );
virtual void PostCollision( vcollisionevent_t *pEvent );
virtual int ShouldSolvePenetration( IPhysicsObject *pObj0, IPhysicsObject *pObj1, void *pGameData0, void *pGameData1, float dt );
virtual void PostSimulationFrame( void );
void PortalPostSimulationFrame( void );
void AddDamageEvent( CBaseEntity *pEntity, const CTakeDamageInfo &info, IPhysicsObject *pInflictorPhysics, bool bRestoreVelocity, const Vector &savedVel, const AngularImpulse &savedAngVel );
static void DisablePenetrationSolving_Push( bool bDisable );
static void DisablePenetrationSolving_Pop( void );
};
#endif //#ifndef PORTAL_PHYSICS_COLLISIONEVENT_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,729 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#ifndef PORTAL_PLAYER_H
#define PORTAL_PLAYER_H
#pragma once
class CPortal_Player;
#include "player.h"
#include "portal_playeranimstate.h"
#include "portal_playerlocaldata.h"
#include "simtimer.h"
#include "soundenvelope.h"
#include "npc_security_camera.h"
#include "portal_player_shared.h"
#include "weapon_portalbase.h"
#include "in_buttons.h"
#include "ai_speech.h" // For expresser host
#include "basemultiplayerplayer.h"
#include "paint_power_user.h"
#include "paintable_entity.h"
#include "ai_basenpc.h"
#include "npc_security_camera.h"
#include "portal_base2d.h"
#if !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR )
#include "portal2_item_inventory.h"
#endif
extern bool UTIL_TimeScaleIsNonStandard( void );
#define PORTAL_COLOR_FLAG_BLUE ( 1 << 0 )
#define PORTAL_COLOR_FLAG_PURPLE ( 1 << 1 )
#define PORTAL_COLOR_FLAG_ORANGE ( 1 << 2 )
#define PORTAL_COLOR_FLAG_RED ( 1 << 3 )
enum PlayerGunType
{
PLAYER_NO_GUN = 0,
PLAYER_PAINT_GUN,
PLAYER_PORTAL_GUN
};
enum ForcedGrabControllerType
{
FORCE_GRAB_CONTROLLER_DEFAULT = 0,
FORCE_GRAB_CONTROLLER_VM,
FORCE_GRAB_CONTROLLER_PHYSICS
};
class CEntityPortalledNetworkMessage
{
public:
DECLARE_CLASS_NOBASE( CEntityPortalledNetworkMessage );
CEntityPortalledNetworkMessage( void );
CHandle<CBaseEntity> m_hEntity;
CHandle<CPortal_Base2D> m_hPortal;
float m_fTime;
bool m_bForcedDuck;
uint32 m_iMessageCount;
};
class CMoveData;
//=============================================================================
// >> Portal_Player
//=============================================================================
class CPortal_Player : public PaintPowerUser< CPaintableEntity< CBaseMultiplayerPlayer > >
#if !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR )
, public IInventoryUpdateListener
#endif
{
public:
DECLARE_CLASS( CPortal_Player, PaintPowerUser< CPaintableEntity< CBaseMultiplayerPlayer > > );
CPortal_Player();
virtual ~CPortal_Player( void );
static CPortal_Player *CreatePlayer( const char *className, edict_t *ed );
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
DECLARE_ENT_SCRIPTDESC();
virtual void Precache( void );
virtual void CreateSounds( void );
virtual void StopLoopingSounds( void );
virtual void Spawn( void );
virtual void SharedSpawn();
virtual void OnRestore( void );
virtual int Restore( IRestore &restore );
virtual void Activate( void );
virtual void Touch( CBaseEntity *pOther );
#ifdef PORTAL2
virtual void InitialSpawn( void );
void SetPlacingPhoto( bool bPlacing );
void OnPhotoAdded( int nIndex );
void OnPhotoRemoved( int nIndex );
void SetSelectedPhoto( int nIndex );
int GetSelectedPhoto( void );
void ClearPhotos( void );
void StripPhotos( bool bNotifyPlayer = true );
void FlashDenyIndicator( float flDuration, unsigned char nType );
void FlashInventory( float flDuration, unsigned char nType );
void Flash( float flDuration, const Vector &vecPosition );
void ControlHelperAnimate( unsigned char nActiveIcon, bool bClear = false );
void UpdateLocatorEntityIndices( int *pIndices, int nNumIndices );
void SetMotionBlurAmount( float flAmt ) { m_flMotionBlurAmount = flAmt; }
#endif // PORTAL2
virtual bool ShouldCollide( int collisionGroup, int contentsMask ) const;
virtual float PlayScene( const char *pszScene, float flDelay, AI_Response *response, IRecipientFilter *filter );
virtual void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params );
virtual void PostThink( void );
virtual void PreThink( void );
void SwapThink();
virtual void PlayerDeathThink();
virtual void PlayerTransitionCompleteThink();
virtual void PlayerCatchPatnerNotConnectingThink();
void UpdatePortalPlaneSounds( void );
void UpdateWooshSounds( void );
Activity TranslateActivity( Activity ActToTranslate, bool *pRequired = NULL );
virtual void Teleport( const Vector *newPosition, const QAngle *newAngles, const Vector *newVelocity, bool bUseSlowHighAccuracyContacts = true );
Activity TranslateTeamActivity( Activity ActToTranslate );
virtual void SetAnimation( PLAYER_ANIM playerAnim );
virtual void PlayerRunCommand(CUserCmd *ucmd, IMoveHelper *moveHelper);
virtual bool ClientCommand( const CCommand &args );
virtual void CreateViewModel( int viewmodelindex = 0 );
virtual bool BecomeRagdollOnClient( const Vector &force );
virtual int OnTakeDamage( const CTakeDamageInfo &inputInfo );
virtual int OnTakeDamage_Alive( const CTakeDamageInfo &info );
virtual void Break( CBaseEntity *pBreaker, const CTakeDamageInfo &info );
virtual bool WantsLagCompensationOnEntity( const CBasePlayer *pPlayer, const CUserCmd *pCmd, const CBitVec<MAX_EDICTS> *pEntityTransmitBits ) const;
virtual void FireBullets ( const FireBulletsInfo_t &info );
virtual bool Weapon_Switch( CBaseCombatWeapon *pWeapon, int viewmodelindex = 0);
virtual Vector Weapon_ShootPosition();
virtual bool BumpWeapon( CBaseCombatWeapon *pWeapon );
virtual CBaseCombatWeapon* Weapon_OwnsThisType( const char *pszWeapon, int iSubType = 0 ) const; // True if already owns a weapon of this class
virtual void Weapon_Equip( CBaseCombatWeapon *pWeapon );
virtual void SelectItem( const char *pstr, int iSubType );
virtual void ShutdownUseEntity( void );
virtual bool ShouldDropActiveWeaponWhenKilled( void ) { return false; }
virtual Vector EyeDirection3D( void );
virtual Vector EyeDirection2D( void );
virtual const Vector &WorldSpaceCenter() const;
//virtual bool StartReplayMode( float fDelay, float fDuration, int iEntity );
//virtual void StopReplayMode();
virtual void Event_Killed( const CTakeDamageInfo &info );
virtual void Jump( void );
void UnDuck( void );
bool UseFoundEntity( CBaseEntity *pUseEntity, bool bAutoGrab );
bool IsInvalidHandoff( CBaseEntity *pObject );
void PollForUseEntity( bool bBasicUse, CBaseEntity **ppUseEnt, CPortal_Base2D **ppUseThroughPortal );
CBaseEntity* FindUseEntity( CPortal_Base2D **pThroughPortal );
CBaseEntity* FindUseEntityThroughPortal( void );
void ZoomIn( void );
void ZoomOut( void );
virtual bool IsZoomed( void );
virtual void PlayerUse( void );
//virtual bool StartObserverMode( int mode );
virtual void GetStepSoundVelocities( float *velwalk, float *velrun );
virtual void PlayStepSound( Vector &vecOrigin, surfacedata_t *psurface, float fvol, bool force );
virtual void UpdateOnRemove( void );
virtual void OnSave( IEntitySaveUtils *pUtils );
virtual void SetupVisibility( CBaseEntity *pViewEntity, unsigned char *pvs, int pvssize );
virtual void UpdatePortalViewAreaBits( unsigned char *pvs, int pvssize );
virtual void ItemPostFrame( void );
bool ValidatePlayerModel( const char *pModel );
void ClearScriptedInteractions( void );
void ParseScriptedInteractions( void );
void AddScriptedInteraction( ScriptedNPCInteraction_t *pInteraction );
CUtlVector<ScriptedNPCInteraction_t> *GetScriptedInteractions( void ) { return &m_ScriptedInteractions; }
void FireConcept( const char *pConcept );
bool IsTaunting( void );
int GetTeamTauntState( void ) const { return m_nTeamTauntState; }
void SetTeamTauntState( int nTeamTauntState );
Vector GetTeamTauntPosition( void ) { return m_vTauntPosition; }
QAngle GetTeamTauntAngles( void ) { return m_vTauntAngles; }
QAngle GetTrickFireAngles( void ) { return m_vPreTauntAngles; }
bool IsTrickFiring( void ) const { return m_bTrickFire; }
void ClearTrickFiring( void ) { m_bTrickFire = false; }
QAngle GetAnimEyeAngles( void ) { return m_angEyeAngles.Get(); }
Vector GetAttackSpread( CBaseCombatWeapon *pWeapon, CBaseEntity *pTarget = NULL );
virtual void CheatImpulseCommands( int iImpulse );
void CreateRagdollEntity( const CTakeDamageInfo &info );
void GiveAllItems( void );
void GiveDefaultItems( void );
void NoteWeaponFired( void );
void ResetAnimation( void );
void SetPlayerModel( void );
int GetPlayerModelType( void ) { return m_iPlayerSoundType; }
virtual void ForceDuckThisFrame( void );
//void UnDuck ( void );
//inline void ForceJumpThisFrame( void ) { ForceButtons( IN_JUMP ); }
void DoAnimationEvent( PlayerAnimEvent_t event, int nData );
void SetupBones( matrix3x4a_t *pBoneToWorld, int boneMask );
// physics interactions
virtual void PickupObject(CBaseEntity *pObject, bool bLimitMassAndSize );
virtual void ForceDropOfCarriedPhysObjects( CBaseEntity *pOnlyIfHoldingThis );
void ToggleHeldObjectOnOppositeSideOfPortal( void ) { m_bHeldObjectOnOppositeSideOfPortal = !m_bHeldObjectOnOppositeSideOfPortal; }
void SetHeldObjectOnOppositeSideOfPortal( bool p_bHeldObjectOnOppositeSideOfPortal ) { m_bHeldObjectOnOppositeSideOfPortal = p_bHeldObjectOnOppositeSideOfPortal; }
bool IsHeldObjectOnOppositeSideOfPortal( void ) { return m_bHeldObjectOnOppositeSideOfPortal; }
CPortal_Base2D *GetHeldObjectPortal( void ) const { return m_hHeldObjectPortal.Get(); }
void SetHeldObjectPortal( CPortal_Base2D *pPortal ) { m_hHeldObjectPortal = pPortal; }
void SetUsingVMGrabState( bool bState ) { m_bUsingVMGrabState = bState; }
bool IsUsingVMGrab( void );
bool WantsVMGrab( void );
void UpdateVMGrab( CBaseEntity *pEntity );
bool IsForcingDrop( void ) { return m_bForcingDrop; }
// This is set by the client when it picks something up
// and used to initiate server grab logic. If we actually pick something up
// it's held in 'm_hAttachedObject'.
EHANDLE m_hGrabbedEntity;
EHANDLE m_hPortalThroughWhichGrabOccured;
bool m_bForcingDrop;
CNetworkVar( bool, m_bUseVMGrab );
CNetworkVar( bool, m_bUsingVMGrabState );
float m_flUseKeyStartTime; // for long duration uses, record the initial keypress start time
float m_flAutoGrabLockOutTime;
void SetForcedGrabControllerType( ForcedGrabControllerType type );
ForcedGrabControllerType m_ForcedGrabController;
// Object we're successfully holding we network down to the client
// for clientside simulation under multiplayer
CNetworkHandle( CBaseEntity, m_hAttachedObject );
CNetworkQAngle( m_vecCarriedObjectAngles );
//not simulating physics on the client, network down any inability the held object has in reaching it's target position/orientation
CNetworkVector( m_vecCarriedObject_CurPosToTargetPos );
CNetworkQAngle( m_vecCarriedObject_CurAngToTargetAng );
void SetUseKeyCooldownTime( float flCooldownDuration );
void SetStuckOnPortalCollisionObject( void ) { m_bStuckOnPortalCollisionObject = true; }
CWeaponPortalBase* GetActivePortalWeapon() const;
void IncrementPortalsPlaced( bool bSecondaryPortal );
void IncrementStepsTaken( void );
void IncrementDistanceTaken( void );
void UpdateSecondsTaken( void );
void ResetThisLevelStats( void );
int NumPortalsPlaced( void ) const { return m_StatsThisLevel.iNumPortalsPlaced; }
int NumStepsTaken( void ) const { return m_StatsThisLevel.iNumStepsTaken; }
float NumSecondsTaken( void ) const { return m_StatsThisLevel.fNumSecondsTaken; }
float NumDistanceTaken( void ) const { return m_StatsThisLevel.fDistanceTaken; }
bool IsHoldingEntity( CBaseEntity *pEnt );
float GetHeldObjectMass( IPhysicsObject *pHeldObject );
void SetNeuroToxinDamageTime( float fCountdownSeconds ) { m_fNeuroToxinDamageTime = gpGlobals->curtime + fCountdownSeconds; }
void IncNumCamerasDetatched( void ) { ++m_iNumCamerasDetatched; }
int GetNumCamerasDetatched( void ) const { return m_iNumCamerasDetatched; }
void MarkClientCheckPVSDirty( void ) { m_bClientCheckPVSDirty = true; }
void SetIsHoldingObject( bool bSet ) { m_bIsHoldingSomething = bSet; }
virtual void ApplyPortalTeleportation( const CPortal_Base2D *pEnteredPortal, CMoveData *pMove );
Vector m_vecTotalBulletForce; //Accumulator for bullet force in a single frame
bool m_bSilentDropAndPickup;
// Tracks our ragdoll entity.
CNetworkHandle( CBaseEntity, m_hRagdoll ); // networked entity handle
#if USE_SLOWTIME
bool IsSlowingTime( void ) { return ( m_PortalLocal.m_bSlowingTime || UTIL_TimeScaleIsNonStandard() ); }
#endif // USE_SLOWTIME
void SetAirControlSupressionTime( float flDuration ) { m_PortalLocal.m_flAirControlSupressionTime = flDuration; }
bool IsSuppressingAirControl( void ) { return m_PortalLocal.m_flAirControlSupressionTime != 0.0f; }
virtual bool TestHitboxes( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr );
virtual void ModifyOrAppendCriteria( AI_CriteriaSet& criteriaSet );
float GetImplicitVerticalStepSpeed() const;
void SetImplicitVerticalStepSpeed( float speed );
const CPortalPlayerLocalData& GetPortalPlayerLocalData() const;
virtual Vector EyePosition();
#if USE_SLOWTIME
// Time modulation
void StartSlowingTime( float flDuration );
void StopSlowingTime( void );
#endif // USE_SLOWTIME
void ShowViewFinder( void );
void HideViewFinder( void );
// Coop ping effect
void PlayCoopPingEffect( void );
CNetworkVar( bool, m_bPitchReorientation );
CNetworkHandle( CPortal_Base2D, m_hPortalEnvironment ); //if the player is in a portal environment, this is the associated portal
friend class CPortal_Base2D;
virtual CBaseEntity* EntSelectSpawnPoint( void );
void PickTeam( void );
static void ClientDisconnected( edict_t *pPlayer );
virtual void ChangeTeam( int iTeamNum );
int FlashlightIsOn( void );
bool FlashlightTurnOn( bool playSound /*= false*/ );
void FlashlightTurnOff( bool playSound /*= false*/ );
void SetInTractorBeam( CTrigger_TractorBeam *pTractorBeam );
void SetLeaveTractorBeam( CTrigger_TractorBeam *pTractorBeam, bool bKeepFloating );
CTrigger_TractorBeam* GetTractorBeam( void ) const { return m_PortalLocal.m_hTractorBeam.Get(); }
friend class CPortalGameMovement;
void PreventCrouchJump( CUserCmd* ucmd );
void BridgeRemovedFromUnder( void );
void WasDroppedByOtherPlayerWhileTaunting() { m_bWasDroppedByOtherPlayerWhileTaunting = true; }
void OnPlayerLanded();
void IncWheatleyMonitorDestructionCount( void ) { m_nWheatleyMonitorDestructionCount++; }
int GetWheatleyMonitorDestructionCount( void ) { return m_nWheatleyMonitorDestructionCount; }
void TurnOffPotatos( void ) { m_bPotatos = false; }
void TurnOnPotatos( void ) { m_bPotatos = true; }
void PlayCoopStepSound( const Vector& origin, int side, float volume );
protected:
mutable Vector m_vWorldSpaceCenterHolder; //WorldSpaceCenter() returns a reference, need an actual value somewhere
CNetworkVarEmbedded( CPortalPlayerLocalData, m_PortalLocal );
CHandle< CNPC_SecurityCamera > m_hRemoteTauntCamera;
void RespawnPlayer( void );
private:
virtual const char *GetPlayerModelName( void );
void PlayUseDenySound( void );
public:
void Taunt( const char *pchTauntForce = NULL, bool bAuto = false );
private:
bool SolveTeamTauntPositionAndAngles( CPortal_Player *pInitiator );
bool ValidateTeamTaunt( CPortal_Player *pInitiator, Vector &vInitiatorPos, QAngle &angInitiatorAng, Vector &vAcceptorPos, QAngle &angAcceptorAng, bool bRecursed = false );
void StartTaunt( void );
bool FindRemoteTauntViewpoint( Vector *pOriginOut, QAngle *pAnglesOut );
#if USE_SLOWTIME
CBaseEntity *m_pSlowTimeColorFX;
#endif // USE_SLOWTIME
CSoundPatch *m_pWooshSound;
CSoundPatch *m_pGrabSound;
int m_nWheatleyMonitorDestructionCount;
CNetworkQAngleXYZ( m_angEyeAngles );
CPortalPlayerAnimState* m_PlayerAnimState;
int m_iLastWeaponFireUsercmd;
CNetworkVar( int, m_iSpawnInterpCounter );
CNetworkVar( int, m_iPlayerSoundType );
CUtlVector<ScriptedNPCInteraction_t> m_ScriptedInteractions;
CNetworkVar( bool, m_bPingDisabled );
CNetworkVar( bool, m_bTauntDisabled );
CNetworkVar( bool, m_bTauntRemoteView );
bool m_bTauntRemoteViewFOVFixup;
CNetworkVector( m_vecRemoteViewOrigin );
CNetworkQAngle( m_vecRemoteViewAngles );
CNetworkVar( float, m_fTauntCameraDistance );
CNetworkVar( int, m_nTeamTauntState );
CNetworkVector( m_vTauntPosition );
CNetworkQAngle( m_vTauntAngles );
CNetworkQAngle( m_vPreTauntAngles );
CNetworkVar( bool, m_bTrickFire );
CNetworkHandle( CPortal_Player, m_hTauntPartnerInRange );
CNetworkString( m_szTauntForce, PORTAL2_MP_TEAM_TAUNT_FORCE_LENGTH );
CNetworkVar( bool, m_bHeldObjectOnOppositeSideOfPortal );
CNetworkHandle( CPortal_Base2D, m_hHeldObjectPortal ); // networked entity handle
#if USE_SLOWTIME
bool m_bHasPlayedSlowTimeStopSound;
#endif // USE_SLOWTIME
bool m_bIntersectingPortalPlane;
bool m_bStuckOnPortalCollisionObject;
bool m_bPlayUseDenySound; // Signaled by PlayerUse, but can be unset by HL2 ladder code...
float m_fNeuroToxinDamageTime;
CNetworkVarEmbedded( PortalPlayerStatistics_t, m_StatsThisLevel );
float m_fTimeLastNumSecondsUpdate;
Vector m_vPrevPosition;
int m_iNumCamerasDetatched;
// In multiplayer, last time we used a coop ping to draw our partner's attention
float m_flLastPingTime;
// When a portal is placed in the same cluster as us, we flip this on
// to signal the updating of the g_ClientCheck's cached PVS bits.
bool m_bClientCheckPVSDirty;
float m_flUseKeyCooldownTime; // Disable use key until curtime >= this number
CNetworkVar( bool, m_bIsHoldingSomething );
float m_flImplicitVerticalStepSpeed; // When moving with step code, the player has an implicit vertical
// velocity that keeps her on ramps, steps, etc. We need this to
// correctly transform her velocity when she teleports.
// to check if the player just respawned on client
CNetworkVar( bool, m_iSpawnCounter );
// to check if the player was dropped from the bridge by the other player
bool m_bWasDroppedByOtherPlayerWhileTaunting;
public: // PAINT SPECIFIC
bool IsReorienting() const;
Vector GetPaintGunShootPosition();
bool IsPressingJumpKey() const;
bool IsHoldingJumpKey() const;
bool IsTryingToSuperJump( const PaintPowerInfo_t* pInfo = NULL ) const;
void SetJumpedThisFrame( bool jumped );
bool JumpedThisFrame() const;
void SetBouncedThisFrame( bool bounced );
bool BouncedThisFrame() const;
InAirState GetInAirState() const;
bool WantsToSwapGuns( void );
void SetWantsToSwapGuns( bool bWantsToSwap );
bool IsUsingPostTeleportationBox() const;
const Vector& GetInputVector() const;
void SetInputVector( const Vector& vInput );
virtual Vector BodyTarget( const Vector &posSrc, bool bNoisy);
const Vector& GetPrevGroundNormal() const;
void SetPrevGroundNormal( const Vector& vPrevNormal );
float PredictedAirTimeEnd( void ); // Uses the current velocity
float PredictedBounce( void ); // Uses the current velocity
void OnBounced( float fTimeOffset = 0.0f );
virtual void SetFogController( CFogController *pFogController );
virtual PaintPowerType GetPaintPowerAtPoint( const Vector& worldContactPt ) const;
virtual void Paint( PaintPowerType type, const Vector& worldContactPt );
virtual void CleansePaint();
void Reorient( QAngle& viewAngles );
float GetReorientationProgress() const;
bool IsDoneReorienting() const;
virtual const Vector GetPlayerMins() const;
virtual const Vector GetPlayerMaxs() const;
const Vector& GetHullMins() const;
const Vector& GetHullMaxs() const;
const Vector& GetStandHullMins() const;
const Vector& GetStandHullMaxs() const;
const Vector& GetDuckHullMins() const;
const Vector& GetDuckHullMaxs() const;
float GetHullHeight() const;
float GetHullWidth() const;
float GetStandHullHeight() const;
float GetStandHullWidth() const;
float GetDuckHullHeight() const;
float GetDuckHullWidth() const;
virtual void UpdateCollisionBounds();
virtual void InitVCollision( const Vector &vecAbsOrigin, const Vector &vecAbsVelocity );
bool PlayGesture( const char *pGestureName );
void SetAirDuck( bool bDuckedInAir );
StickCameraState GetStickCameraState() const;
void SetQuaternionPunch( const Quaternion& qPunch );
void DecayQuaternionPunch();
using BaseClass::AddSurfacePaintPowerInfo;
void AddSurfacePaintPowerInfo( const BrushContact& contact, char const* context = 0 );
void AddSurfacePaintPowerInfo( const trace_t& trace, char const* context = 0 );
void SetEyeUpOffset( const Vector& vOldUp, const Vector& vNewUp );
void SetEyeOffset( const Vector& vOldOrigin, const Vector& vNewOrigin );
void GivePlayerPaintGun( bool bActivatePaintPowers, bool bSwitchTo );
void GivePlayerPortalGun( bool bUpgraded, bool bSwitchTo );
void GivePlayerWearable( const char *pItemName );
void RemovePlayerWearable( const char *pItemName );
void ResetBounceCount() { m_nBounceCount = 0; }
void ResetAirTauntCount() { m_nAirTauntCount = 0; }
// Anim state code
CNetworkVarEmbedded( CPortalPlayerShared, m_Shared );
void SetHullHeight( float flHeight );
virtual void ChooseActivePaintPowers( PaintPowerInfoVector& activePowers );
bool IsFullyConnected() { return m_bIsFullyConnected; }
void OnFullyConnected();
void NetworkPortalTeleportation( CBaseEntity *pOther, CPortal_Base2D *pPortal, float fTime, bool bForcedDuck );
private: // PAINT SPECIFIC
void DecayEyeOffset();
void GivePortalPlayerItems( void );
// Find all the contacts
void DeterminePaintContacts();
void PredictPaintContacts( const Vector& contactBoxMin,
const Vector& contactBoxMax,
const Vector& traceBoxMin,
const Vector& traceBoxMax,
float lookAheadTime,
char const* context );
void ChooseBestPaintPowersInRange( PaintPowerChoiceResultArray& bestPowers,
PaintPowerConstIter begin,
PaintPowerConstIter end,
const PaintPowerChoiceCriteria_t& info ) const;
// Paint Power User Implementation
virtual PaintPowerState ActivateSpeedPower( PaintPowerInfo_t& powerInfo );
virtual PaintPowerState UseSpeedPower( PaintPowerInfo_t& powerInfo );
virtual PaintPowerState DeactivateSpeedPower( PaintPowerInfo_t& powerInfo );
virtual PaintPowerState ActivateBouncePower( PaintPowerInfo_t& powerInfo );
virtual PaintPowerState UseBouncePower( PaintPowerInfo_t& powerInfo );
virtual PaintPowerState DeactivateBouncePower( PaintPowerInfo_t& powerInfo );
void PlayPaintSounds( const PaintPowerChoiceResultArray& touchedPowers );
void UpdatePaintedPower();
void UpdateAirInputScaleFadeIn();
void UpdateInAirState();
void CachePaintPowerChoiceResults( const PaintPowerChoiceResultArray& choiceInfo );
bool LateSuperJumpIsValid() const;
void RecomputeBoundsForOrientation();
void TryToChangeCollisionBounds( const Vector& newStandHullMin,
const Vector& newStandHullMax,
const Vector& newDuckHullMin,
const Vector& newDuckHullMax );
float SpeedPaintAcceleration( float flDefaultMaxSpeed,
float flSpeed,
float flWishCos,
float flWishDirSpeed ) const;
bool CheckToUseBouncePower( PaintPowerInfo_t& info );
// stick camera
void RotateUpVector( Vector& vForward, Vector& vUp );
void SnapCamera( StickCameraState nCameraState, bool bLookingInBadDirection );
void PostTeleportationCameraFixup( const CPortal_Base2D *pEnteredPortal );
// Paint power debug
void DrawJumpHelperDebug( PaintPowerConstIter begin, PaintPowerConstIter end, float duration, bool noDepthTest, const PaintPowerInfo_t* pSelected ) const;
// PAINT POWER STATE
PaintPowerInfo_t m_CachedJumpPower;
Vector m_vInputVector;
float m_flCachedJumpPowerTime;
float m_flUsePostTeleportationBoxTime;
float m_flSpeedDecelerationTime;
float m_flPredictedJumpTime;
bool m_bJumpWasPressedWhenForced; // The jump button was actually pressed when ForceDuckThisFrame() was called
int m_nBounceCount; // Number of bounces in a row without touching the ground
float m_LastGroundBouncePlaneDistance;
float m_flLastSuppressedBounceTime;
float m_flTimeSinceLastTouchedPower[3];
int m_nPortalsEnteredInAirFlags;
int m_nAirTauntCount;
bool m_bIsFullyConnected;
CNetworkVar( float, m_flMotionBlurAmount );
//Swapping guns
CNetworkVar( bool, m_bWantsToSwapGuns );
bool m_bSendSwapProximityFailEvent;
CNetworkVar( bool, m_bPotatos );
PlayerGunType m_PlayerGunType;
PlayerGunType m_PlayerGunTypeWhenDead;
bool m_bSpawnFromDeath;
bool m_bIsBendy;
Vector m_vPrevGroundNormal; // Our ground normal from the previous frame
Vector m_vGravity;
CNetworkVar( float, m_flHullHeight );
//encoding these messages directly in the player to ensure it's received in sync with the corresponding entity post-teleport update
//each player has their own copy of the buffer that is only sent to them
CUtlVector<CEntityPortalledNetworkMessage> m_EntityPortalledNetworkMessages;
enum
{
MAX_ENTITY_PORTALLED_NETWORK_MESSAGES = 32,
};
CNetworkVar( uint32, m_iEntityPortalledNetworkMessageCount ); //always ticks up by one per add
//variables we'd like to persist between instances of grab controllers
struct GrabControllerPersistentVars_t
{
CHandle<CPortal_Base2D> m_hOscillationWatch;
CHandle<CPortal_Base2D> m_hLookingThroughPortalLastUpdate;
Vector m_vLastTargetPosition;
bool m_bLastUpdateWasForcedPull;
void ResetOscillationWatch( void )
{
m_hOscillationWatch = NULL;
m_hLookingThroughPortalLastUpdate = NULL;
m_vLastTargetPosition.Init();
m_bLastUpdateWasForcedPull = false;
}
};
GrabControllerPersistentVars_t m_GrabControllerPersistentVars;
struct RecentPortalTransform_t
{
int command_number;
CHandle<CPortal_Base2D> Portal;
matrix3x4_t matTransform;
};
CUtlVector<RecentPortalTransform_t> m_PendingPortalTransforms; //portal transforms we've sent to the client but they have not yet acknowledged, needed for some input fixup
friend class CPortalPlayerShared;
friend class CGrabController;
#if !defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR )
//----------------------------
// ECONOMY INVENTORY MANAGEMENT
public:
// IInventoryUpdateListener
virtual void InventoryUpdated( CPlayerInventory *pInventory );
virtual void SOCacheUnsubscribed( const CSteamID & steamIDOwner ) { m_Shared.SetLoadoutUnavailable( true ); }
void UpdateInventory( bool bInit );
void VerifySOCache();
// Inventory access
CPortalPlayerInventory *Inventory( void ) { return &m_Inventory; }
CEconItemView *GetItemInLoadoutSlot( int iLoadoutSlot );
private:
CPortalPlayerInventory m_Inventory;
#endif //!defined( NO_STEAM ) && !defined( NO_STEAM_GAMECOORDINATOR )
bool m_bReadyForDLCItemUpdates;
};
inline CPortal_Player *ToPortalPlayer( CBaseEntity *pEntity )
{
if ( !pEntity || !pEntity->IsPlayer() )
return NULL;
return assert_cast<CPortal_Player*>( pEntity );
}
inline CPortal_Player *GetPortalPlayer( int iPlayerIndex )
{
return static_cast<CPortal_Player*>( UTIL_PlayerByIndex( iPlayerIndex ) );
}
#endif //PORTAL_PLAYER_H

View File

@@ -0,0 +1,450 @@
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
//NOTE: Mirrors with models require an attachment named "MirrorSurface_Attach" with x facing out of the mirror plane.
//They also require that the mirror surface be in a bodygroup by itself named "MirrorSurface" with the first index being the mirror, second being empty.
//Lastly, they require that all non-mirror geometry be in bodygroups that have the second entry as empty.
//It's a good idea to put a cubemap on the mirror surface material because they're not infinitely recursive
#include "cbase.h"
#include "baseanimating.h"
#include "pvs_extender.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//#define TEST_ANIMATION //uncomment to run "testanim" in a loop
class CProp_Mirror : public CBaseAnimating, CPVS_Extender
{
public:
DECLARE_CLASS( CProp_Mirror, CBaseAnimating );
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
CProp_Mirror( void );
virtual void Precache( void );
virtual void Spawn( void );
virtual int UpdateTransmitState() { return SetTransmitState( FL_EDICT_ALWAYS ); }
virtual int ObjectCaps( void ) { return BaseClass::ObjectCaps() | (m_bPhysicsEnabled ? FCAP_IMPULSE_USE : 0); };
virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value );
void UpdateReflectionPlane( void );
void UpdateReflectionPolygon( void );
virtual CServerNetworkProperty *GetExtenderNetworkProp( void ) { return NetworkProp(); }
virtual const edict_t *GetExtenderEdict( void ) const { return edict(); }
virtual Vector GetExtensionPVSOrigin( void ) { return GetAbsOrigin(); }
virtual bool IsExtenderValid( void ) { return true; }
int ComputeFrustumThroughPolygon( const Vector &vVisOrigin, const VPlane *pInputFrustum, int iInputFrustumPlanes, VPlane *pOutputFrustum, int iOutputFrustumMaxPlanes );
//This portal is decidedly visible, recursively extend the visibility problem
virtual void 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 defined( TEST_ANIMATION )
virtual void Think( void );
#endif
Vector m_LocalSpaceReflectionPolygonVerts[10]; //best guess at the reflection polygon by intersecting the reflection plane with the local space OBB
int m_LocalSpaceReflectionPolygonVertCount;
struct ReflectPlaneCachedData_t
{
Vector vAttachmentOrigin;
QAngle qAttachmentAngle;
bool bModel;
Vector vLocalSpaceAttachmentOrigin;
QAngle qLocalSpaceAttachmentAngles;
Vector vLocalOBB_Mins;
Vector vLocalOBB_Maxs;
};
ReflectPlaneCachedData_t m_CachedReflectedData;
VMatrix m_matReflection;
CNetworkVar( float, m_fWidth );
CNetworkVar( float, m_fHeight );
int m_iMirrorFaceAttachment;
bool m_bModel;
bool m_bPhysicsEnabled;
};
BEGIN_DATADESC( CProp_Mirror )
DEFINE_KEYFIELD( m_fWidth, FIELD_FLOAT, "Width" ),
DEFINE_KEYFIELD( m_fHeight, FIELD_FLOAT, "Height" ),
DEFINE_FIELD( m_iMirrorFaceAttachment, FIELD_INTEGER ),
DEFINE_FIELD( m_bModel, FIELD_BOOLEAN ),
DEFINE_KEYFIELD( m_bPhysicsEnabled, FIELD_BOOLEAN, "PhysicsEnabled" ),
END_DATADESC()
IMPLEMENT_SERVERCLASS_ST( CProp_Mirror, DT_Prop_Mirror )
SendPropFloat( SENDINFO(m_fWidth) ),
SendPropFloat( SENDINFO(m_fHeight) ),
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( prop_mirror, CProp_Mirror );
CProp_Mirror::CProp_Mirror( void )
{
m_matReflection.m[3][0] = 0.0f;
m_matReflection.m[3][1] = 0.0f;
m_matReflection.m[3][2] = 0.0f;
m_matReflection.m[3][3] = 1.0f;
m_CachedReflectedData.vAttachmentOrigin.Invalidate();
m_CachedReflectedData.qAttachmentAngle.Invalidate();
m_CachedReflectedData.vLocalSpaceAttachmentOrigin.Invalidate();
m_CachedReflectedData.qLocalSpaceAttachmentAngles.Invalidate();
m_CachedReflectedData.vLocalOBB_Maxs.Invalidate();
m_CachedReflectedData.vLocalOBB_Mins.Invalidate();
}
void CProp_Mirror::Precache( void )
{
BaseClass::Precache();
if( (m_ModelName.ToCStr() != NULL) && (m_ModelName.ToCStr()[0] != '\0') )
{
PrecacheModel( m_ModelName.ToCStr() );
}
}
void CProp_Mirror::Spawn( void )
{
Precache();
BaseClass::Spawn();
if( m_ModelName.ToCStr() != NULL && m_ModelName.ToCStr()[0] != '\0' )
{
SetModel( m_ModelName.ToCStr() );
SetSolid( SOLID_VPHYSICS );
SetCollisionGroup( COLLISION_GROUP_INTERACTIVE );
if( m_bPhysicsEnabled )
{
SetMoveType( MOVETYPE_VPHYSICS );
VPhysicsInitNormal( GetSolid(), GetSolidFlags(), false );
}
else
{
SetMoveType( MOVETYPE_NONE );
}
#if defined( TEST_ANIMATION )
ResetSequence( LookupSequence( "testanim" ) );
ResetSequenceInfo();
SetPlaybackRate( 0.1f );
SetNextThink( gpGlobals->curtime + 1.0f );
#endif
m_iMirrorFaceAttachment = LookupAttachment( "MirrorSurface_Attach" );
m_bModel = ( m_iMirrorFaceAttachment > 0 ); //0 is an invalid attachment index according to LookupAttachment()
}
else
{
Vector vExtent( 2.0f, m_fWidth/2.0f, m_fHeight/2.0f );
SetSize( -vExtent, vExtent );
m_bModel = false;
}
}
void CProp_Mirror::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
if( m_bPhysicsEnabled )
{
CBasePlayer *pPlayer = ToBasePlayer( pActivator );
if ( pPlayer )
{
pPlayer->PickupObject( this );
}
}
else
{
BaseClass::Use( pActivator, pCaller, useType, value );
}
}
void CProp_Mirror::UpdateReflectionPlane( void )
{
Vector vMirrorAttachmentOrigin;
QAngle qMirrorAttachmentAngles;
if( m_bModel )
{
GetAttachment( m_iMirrorFaceAttachment, vMirrorAttachmentOrigin, qMirrorAttachmentAngles );
}
else
{
vMirrorAttachmentOrigin = GetAbsOrigin();
qMirrorAttachmentAngles = GetAbsAngles();
}
if( (m_CachedReflectedData.vAttachmentOrigin != vMirrorAttachmentOrigin) || (m_CachedReflectedData.qAttachmentAngle != qMirrorAttachmentAngles) )
{
m_CachedReflectedData.vAttachmentOrigin = vMirrorAttachmentOrigin;
m_CachedReflectedData.qAttachmentAngle = qMirrorAttachmentAngles;
Vector vOrigin = m_CachedReflectedData.vAttachmentOrigin;
Vector vForward, vRight, vUp;
AngleVectors( qMirrorAttachmentAngles, &vForward, &vRight, &vUp );
Vector vToOrigin( vOrigin.Dot( vForward ), vOrigin.Dot( vRight ), -vOrigin.Dot( vUp ) );
//generate mirroring matrix. Move mirror to origin using base vectors, flip on forward axis, move back to position and orientation
{
m_matReflection.m[0][0] = (-vForward.x * vForward.x) + (vRight.x * vRight.x) + (vUp.x * vUp.x);
m_matReflection.m[0][1] = (-vForward.x * vForward.y) + (vRight.x * vRight.y) + (vUp.x * vUp.y);
m_matReflection.m[0][2] = (-vForward.x * vForward.z) + (vRight.x * vRight.z) + (vUp.x * vUp.z);
m_matReflection.m[0][3] = (vToOrigin.x * vForward.x) - (vToOrigin.y * vRight.x) + (vToOrigin.z * vUp.x) + vOrigin.x;
m_matReflection.m[1][0] = m_matReflection.m[0][1]; //rotation portion of the matrix is equal to it's own transpose
m_matReflection.m[1][1] = (-vForward.y * vForward.y) + (vRight.y * vRight.y) + (vUp.y * vUp.y);
m_matReflection.m[1][2] = (-vForward.y * vForward.z) + (vRight.y * vRight.z) + (vUp.y * vUp.z);
m_matReflection.m[1][3] = (vToOrigin.x * vForward.y) - (vToOrigin.y * vRight.y) + (vToOrigin.z * vUp.y) + vOrigin.y;
m_matReflection.m[2][0] = m_matReflection.m[0][2]; //rotation portion of the matrix is equal to it's own transpose
m_matReflection.m[2][1] = m_matReflection.m[1][2]; //rotation portion of the matrix is equal to it's own transpose
m_matReflection.m[2][2] = (-vForward.z * vForward.z) + (vRight.z * vRight.z) + (vUp.z * vUp.z);
m_matReflection.m[2][3] = (vToOrigin.x * vForward.z) - (vToOrigin.y * vRight.z) + (vToOrigin.z * vUp.z) + vOrigin.z;
}
UpdateReflectionPolygon();
}
}
void CProp_Mirror::UpdateReflectionPolygon( void )
{
if( m_bModel != m_CachedReflectedData.bModel )
{
m_CachedReflectedData.qAttachmentAngle.Invalidate();
m_CachedReflectedData.vLocalSpaceAttachmentOrigin.Invalidate();
m_CachedReflectedData.qLocalSpaceAttachmentAngles.Invalidate();
m_CachedReflectedData.vLocalOBB_Maxs.Invalidate();
m_CachedReflectedData.bModel = m_bModel;
}
if( m_bModel )
{
Vector vMins, vMaxs;
vMins = WorldAlignMins();
vMaxs = WorldAlignMaxs();
Vector vLocalAttachmentOrigin;
QAngle qLocalAttachmentAngles;
GetAttachmentLocal( m_iMirrorFaceAttachment, vLocalAttachmentOrigin, qLocalAttachmentAngles );
if( (vMins == m_CachedReflectedData.vLocalOBB_Mins) && (vMaxs == m_CachedReflectedData.vLocalOBB_Maxs) &&
(vLocalAttachmentOrigin == m_CachedReflectedData.vLocalSpaceAttachmentOrigin) && (qLocalAttachmentAngles == m_CachedReflectedData.qLocalSpaceAttachmentAngles) )
{
return; //nothing to update
}
m_CachedReflectedData.vLocalOBB_Mins = vMins;
m_CachedReflectedData.vLocalOBB_Maxs = vMaxs;
m_CachedReflectedData.vLocalSpaceAttachmentOrigin = vLocalAttachmentOrigin;
m_CachedReflectedData.qLocalSpaceAttachmentAngles = qLocalAttachmentAngles;
Vector vAttachmentVectors[3];
AngleVectors( qLocalAttachmentAngles, &vAttachmentVectors[0], &vAttachmentVectors[1], &vAttachmentVectors[2] );
float fLargestOBBDiff = vMaxs.x - vMins.x;
for( int i = 1; i != 3; ++i )
{
float fDiff = vMaxs[i] - vMins[i];
if( fDiff > fLargestOBBDiff )
{
fLargestOBBDiff = fDiff;
}
}
fLargestOBBDiff *= 4.0f; //to easily cover diagonal intersection and then some
Vector vClipBuffers[2][10]; //4 starting points, possible to create 1 extra point per cut, 6 cuts
vClipBuffers[0][0] = vLocalAttachmentOrigin + (vAttachmentVectors[1] * fLargestOBBDiff) + (vAttachmentVectors[2] * fLargestOBBDiff);
vClipBuffers[0][1] = vLocalAttachmentOrigin - (vAttachmentVectors[1] * fLargestOBBDiff) + (vAttachmentVectors[2] * fLargestOBBDiff);
vClipBuffers[0][2] = vLocalAttachmentOrigin - (vAttachmentVectors[1] * fLargestOBBDiff) - (vAttachmentVectors[2] * fLargestOBBDiff);
vClipBuffers[0][3] = vLocalAttachmentOrigin + (vAttachmentVectors[1] * fLargestOBBDiff) - (vAttachmentVectors[2] * fLargestOBBDiff);
int iVertCount = 4;
VPlane vClipPlanes[6];
vClipPlanes[0].Init( Vector( 1.0f, 0.0f, 0.0f ), vMins.x );
vClipPlanes[1].Init( Vector( -1.0f, 0.0f, 0.0f ), -vMaxs.x );
vClipPlanes[2].Init( Vector( 0.0f, 1.0f, 0.0f ), vMins.y );
vClipPlanes[3].Init( Vector( 0.0f, -1.0f, 0.0f ), -vMaxs.y );
vClipPlanes[4].Init( Vector( 0.0f, 0.0f, 1.0f ), vMins.z );
vClipPlanes[5].Init( Vector( 0.0f, 0.0f, -1.0f ), -vMaxs.z );
for( int i = 0; i != 6; ++i )
{
iVertCount = ClipPolyToPlane( vClipBuffers[i & 1], iVertCount, vClipBuffers[(i & 1) ^ 1], vClipPlanes[i].m_Normal, vClipPlanes[i].m_Dist, 0.01f );
}
Assert( iVertCount >= 3 );
m_LocalSpaceReflectionPolygonVertCount = iVertCount;
memcpy( m_LocalSpaceReflectionPolygonVerts, vClipBuffers[0], sizeof( Vector ) * iVertCount );
}
else
{
if( (m_CachedReflectedData.vLocalOBB_Maxs.x == m_fWidth) && (m_CachedReflectedData.vLocalOBB_Maxs.y == m_fHeight) )
return;
m_LocalSpaceReflectionPolygonVertCount = 4;
float fHalfWidth = m_fWidth / 2.0f;
float fHalfHeight = m_fHeight / 2.0f;
m_LocalSpaceReflectionPolygonVerts[0].Init( 0.0f, fHalfWidth, fHalfHeight );
m_LocalSpaceReflectionPolygonVerts[1].Init( 0.0f, -fHalfWidth, fHalfHeight );
m_LocalSpaceReflectionPolygonVerts[2].Init( 0.0f, -fHalfWidth, -fHalfHeight );
m_LocalSpaceReflectionPolygonVerts[3].Init( 0.0f, fHalfWidth, -fHalfHeight );
}
}
#if defined( TEST_ANIMATION )
void CProp_Mirror::Think( void )
{
StudioFrameAdvance();
DispatchAnimEvents( this );
if (IsSequenceFinished() && !SequenceLoops())
{
// ResetSequenceInfo();
// hack to avoid reloading model every frame
m_flAnimTime = gpGlobals->curtime;
m_flPlaybackRate = 0.1f;
m_bSequenceFinished = false;
m_flLastEventCheck = 0;
m_flCycle = 0;
}
SetNextThink( gpGlobals->curtime + 0.1f );
}
#endif
int CProp_Mirror::ComputeFrustumThroughPolygon( const Vector &vVisOrigin, const VPlane *pInputFrustum, int iInputFrustumPlanes, VPlane *pOutputFrustum, int iOutputFrustumMaxPlanes )
{
Vector vTransformedPolyVerts[10];
const matrix3x4_t &matLocalToWorld = CollisionProp()->CollisionToWorldTransform();
for( int i = 0; i != m_LocalSpaceReflectionPolygonVertCount; ++i )
{
VectorTransform( &m_LocalSpaceReflectionPolygonVerts[i].x, matLocalToWorld, &vTransformedPolyVerts[i].x );
}
int iReturnedPlanes = UTIL_CalcFrustumThroughConvexPolygon( vTransformedPolyVerts, m_LocalSpaceReflectionPolygonVertCount, vVisOrigin, pInputFrustum, iInputFrustumPlanes, pOutputFrustum, iOutputFrustumMaxPlanes, 0 );
if( (iReturnedPlanes < iOutputFrustumMaxPlanes) && (iReturnedPlanes != 0) )
{
Vector vForward;
AngleVectors( m_CachedReflectedData.qAttachmentAngle, &vForward );
vForward = -vForward;
//add the reflection plane as a near plane
pOutputFrustum[iReturnedPlanes].Init( vForward, vForward.Dot( m_CachedReflectedData.vAttachmentOrigin ) );
++iReturnedPlanes;
}
return iReturnedPlanes;
}
void CProp_Mirror::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;
UpdateReflectionPlane();
Vector vForward;
AngleVectors( m_CachedReflectedData.qAttachmentAngle, &vForward );
if( vForward.Dot( vVisOrigin ) < vForward.Dot( m_CachedReflectedData.vAttachmentOrigin ) )
return; //vis origin is behind the reflection plane
//both test if the portal is within the view frustum, and calculate the new one at the same time
int iFrustumPlanesMax = (iVisFrustumPlanes + m_LocalSpaceReflectionPolygonVertCount + 1);
VPlane *pNewFrustum = (VPlane *)stackalloc( sizeof( VPlane ) * iFrustumPlanesMax );
int iNewFrustumPlanes = ComputeFrustumThroughPolygon( vVisOrigin, pVisFrustum, iVisFrustumPlanes, pNewFrustum, iFrustumPlanesMax );
if( iNewFrustumPlanes == 0 )
{
return;
}
//NDebugOverlay::EntityBounds( this, 0, 255, 0, 100, 0.0f );
int iArea = NetworkProp()->AreaNum();
unsigned char *pPVS = m_pExtenderData->iPVSBits;
if( !m_pExtenderData->bAddedToPVSAlready )
{
bool bFound = false;
for( int i = 0; i != MAX_MAP_AREAS; ++i )
{
if( iAreasNetworked[i] == iArea )
{
bFound = true;
break;
}
if( iAreasNetworked[i] == -1 )
{
bFound = true; //we found it by adding it
iAreasNetworked[i] = iArea;
int iOutputPVSIntSize = pvssize / sizeof( unsigned int );
for( int j = 0; j != iOutputPVSIntSize; ++j )
{
((unsigned int *)outputPVS)[j] |= ((unsigned int *)pPVS)[j];
}
for( int j = iOutputPVSIntSize * sizeof( unsigned int ); j != pvssize; ++j )
{
outputPVS[j] |= pPVS[j];
}
break;
}
}
Vector vForward;
AngleVectors( m_CachedReflectedData.qAttachmentAngle, &vForward, NULL, NULL );
engine->AddOriginToPVS( m_CachedReflectedData.vAttachmentOrigin + vForward );
m_pExtenderData->bAddedToPVSAlready = true;
}
--iMaxRecursionsLeft;
if( iMaxRecursionsLeft == 0 )
return;
edict_t *myEdict = edict();
VisExtensionChain_t chainNode;
chainNode.m_nArea = iArea;
chainNode.pParentChain = pVisChain;
//transform vis origin to linked space
Vector vTransformedVisOrigin = m_matReflection * vVisOrigin;
Vector vTranslation = m_matReflection.GetTranslation();
//transform the planes into the linked portal space
for( int i = 0; i != iNewFrustumPlanes; ++i )
{
pNewFrustum[i].m_Normal = m_matReflection.ApplyRotation( pNewFrustum[i].m_Normal );
pNewFrustum[i].m_Dist += pNewFrustum[i].m_Normal.Dot( vTranslation );
}
Assert( pPVS != 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() == myEdict )
continue;
if ( pExtender->GetExtenderNetworkProp()->IsInPVS( myEdict, pPVS, (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 );
}
}
}

View File

@@ -0,0 +1,988 @@
//===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#include "cbase.h"
#include "prop_portal_shared.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 "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 "baseprojector.h"
#include "tier1/convar.h"
#include "iextpropportallocator.h"
#include "matchmaking/imatchframework.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#ifdef PORTAL2
extern bool UTIL_FizzlePlayerPhotos( CPortal_Player *pPlayer );
#endif // PORTAL2
static CUtlVector<CProp_Portal *> s_PortalLinkageGroups[256];
const char *CProp_Portal::s_szDelayedPlacementThinkContext = "CProp_Portal::DelayedPlacementThink";
extern ConVar sv_portal_placement_never_fail;
extern ConVar use_server_portal_particles;
BEGIN_DATADESC( CProp_Portal )
//saving
DEFINE_KEYFIELD( m_iLinkageGroupID, FIELD_CHARACTER, "LinkageGroupID" ),
DEFINE_KEYFIELD( m_bActivated, FIELD_BOOLEAN, "Activated" ),
DEFINE_KEYFIELD( m_bOldActivatedState, FIELD_BOOLEAN, "OldActivated" ),
DEFINE_KEYFIELD( m_bIsPortal2, FIELD_BOOLEAN, "PortalTwo" ),
DEFINE_FIELD( m_NotifyOnPortalled, FIELD_EHANDLE ),
DEFINE_FIELD( m_hFiredByPlayer, FIELD_EHANDLE ),
DEFINE_SOUNDPATCH( m_pAmbientSound ),
// Function Pointers
DEFINE_THINKFUNC( DelayedPlacementThink ),
DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetActivatedState", InputSetActivatedState ),
DEFINE_INPUTFUNC( FIELD_VOID, "Fizzle", InputFizzle ),
DEFINE_INPUTFUNC( FIELD_STRING, "NewLocation", InputNewLocation ),
DEFINE_INPUTFUNC( FIELD_STRING, "Resize", InputResize ),
DEFINE_INPUTFUNC( FIELD_INTEGER, "SetLinkageGroupId", InputSetLinkageGroupId ),
END_DATADESC()
IMPLEMENT_SERVERCLASS_ST( CProp_Portal, DT_Prop_Portal )
SendPropEHandle( SENDINFO( m_hFiredByPlayer ) ),
SendPropInt( SENDINFO( m_nPlacementAttemptParity ), EF_PARITY_BITS, SPROP_UNSIGNED ),
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( prop_portal, CProp_Portal );
CProp_Portal::CProp_Portal( void )
{
if( !ms_DefaultPortalSizeInitialized )
{
ms_DefaultPortalSizeInitialized = true; // for CEG protection
CEG_GCV_PRE();
ms_DefaultPortalHalfHeight = CEG_GET_CONSTANT_VALUE( DefaultPortalHalfHeight ); // only protecting one to reduce the cost of first-portal check
CEG_GCV_POST();
}
m_FizzleEffect = PORTAL_FIZZLE_KILLED;
CProp_Portal_Shared::AllPortals.AddToTail( this );
}
CProp_Portal::~CProp_Portal( void )
{
CProp_Portal_Shared::AllPortals.FindAndRemove( this );
s_PortalLinkageGroups[m_iLinkageGroupID].FindAndRemove( this );
}
void CProp_Portal::Precache( void )
{
PrecacheScriptSound( "Portal.ambient_loop" );
PrecacheScriptSound( "Portal.open_blue" );
PrecacheScriptSound( "Portal.open_red" );
PrecacheScriptSound( "Portal.close_blue" );
PrecacheScriptSound( "Portal.close_red" );
PrecacheScriptSound( "Portal.fizzle_moved" );
PrecacheScriptSound( "Portal.fizzle_invalid_surface" );
PrecacheModel( "models/portals/portal1.mdl" );
PrecacheModel( "models/portals/portal2.mdl" );
//PrecacheParticleSystem( "portal_1_particles" );
//PrecacheParticleSystem( "portal_2_particles" );
//PrecacheParticleSystem( "portal_1_edge" );
//PrecacheParticleSystem( "portal_2_edge" );
//PrecacheParticleSystem( "portal_1_close" );
//PrecacheParticleSystem( "portal_2_close" );
//PrecacheParticleSystem( "portal_1_badsurface" );
//PrecacheParticleSystem( "portal_2_badsurface" );
//PrecacheParticleSystem( "portal_1_success" );
//PrecacheParticleSystem( "portal_2_success" );
// adjustable color for coop, two colorable systems instead of four unique -mtw
// need two systems here because they spin different directions
PrecacheParticleSystem( "portal_edge" );
PrecacheParticleSystem( "portal_edge_reverse" );
PrecacheParticleSystem( "portal_close" );
PrecacheParticleSystem( "portal_badsurface" );
PrecacheParticleSystem( "portal_success" );
BaseClass::Precache();
}
void CProp_Portal::CreateSounds()
{
if (!m_pAmbientSound)
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
CPASAttenuationFilter filter( this );
m_pAmbientSound = controller.SoundCreate( filter, entindex(), "Portal.ambient_loop" );
controller.Play( m_pAmbientSound, 0, 100 );
}
}
void CProp_Portal::StopLoopingSounds()
{
if ( m_pAmbientSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundDestroy( m_pAmbientSound );
m_pAmbientSound = NULL;
}
BaseClass::StopLoopingSounds();
}
class CPortalServerDllPropPortalLocator : public IPortalServerDllPropPortalLocator
{
public:
virtual void LocateAllPortals( CUtlVector<PortalInfo_t> &arrPortals )
{
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;
const Vector &vecOrigin = pPortal->GetAbsOrigin();
const QAngle &vecAngle = pPortal->GetAbsAngles();
PortalInfo_t pi;
pi.iLinkageGroupId = iLinkageGroupID;
pi.nPortal = nPortal;
pi.vecOrigin = vecOrigin;
pi.vecAngle = vecAngle;
arrPortals.AddToTail( pi );
}
}
}
} s_PortalServerDllPropPortalLocator;
void CProp_Portal::Spawn( void )
{
Precache();
AddToLinkageGroup();
ResetModel();
if( (GetHalfWidth() <= 0) || (GetHalfHeight() <= 0) )
Resize( ms_DefaultPortalHalfWidth, ms_DefaultPortalHalfHeight );
BaseClass::Spawn();
static bool s_bPortalLocatorForClientRegistered;
if ( !s_bPortalLocatorForClientRegistered && g_pMatchFramework )
{
s_bPortalLocatorForClientRegistered = true;
g_pMatchFramework->GetMatchExtensions()->RegisterExtensionInterface( IEXTPROPPORTALLOCATOR_INTERFACE_NAME, &s_PortalServerDllPropPortalLocator );
}
}
void CProp_Portal::OnRestore()
{
BaseClass::OnRestore();
if ( IsActive() )
{
// Place the particles in position
DispatchPortalPlacementParticles( m_bIsPortal2 );
}
AddToLinkageGroup();
}
ConVar sv_portals_block_other_players( "sv_portals_block_other_players", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
void CProp_Portal::StartTouch( CBaseEntity *pOther )
{
if( sv_portals_block_other_players.GetBool() && g_pGameRules->IsMultiplayer() )
{
if( pOther->IsPlayer() && (m_hFiredByPlayer.Get() != pOther) )
return; //block the interaction
}
return BaseClass::StartTouch( pOther );
}
void CProp_Portal::Touch( CBaseEntity *pOther )
{
if( sv_portals_block_other_players.GetBool() && g_pGameRules->IsMultiplayer() )
{
if( pOther->IsPlayer() && (m_hFiredByPlayer.Get() != pOther) )
return; //block the interaction
}
return BaseClass::Touch( pOther );
}
void CProp_Portal::EndTouch( CBaseEntity *pOther )
{
if( sv_portals_block_other_players.GetBool() && g_pGameRules->IsMultiplayer() )
{
if( pOther->IsPlayer() && (m_hFiredByPlayer.Get() != pOther) )
return; //block the interaction
}
return BaseClass::EndTouch( pOther );
}
void DumpActiveCollision( const CPortalSimulator *pPortalSimulator, const char *szFileName );
void PortalSimulatorDumps_DumpCollideToGlView( CPhysCollide *pCollide, const Vector &origin, const QAngle &angles, float fColorScale, const char *pFilename );
void CProp_Portal::ResetModel( void )
{
if( !m_bIsPortal2 )
SetModel( "models/portals/portal1.mdl" );
else
SetModel( "models/portals/portal2.mdl" );
if( IsMobile() || ((m_hLinkedPortal.Get() != NULL) && !m_hLinkedPortal->IsMobile()) )
{
SetSize( GetLocalMins(), Vector( 4.0f, m_fNetworkHalfWidth, m_fNetworkHalfHeight ) );
}
else
{
SetSize( GetLocalMins(), GetLocalMaxs() );
}
SetSolid( SOLID_OBB );
SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID | FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
}
void CProp_Portal::DoFizzleEffect( int iEffect, bool bDelayedPos /*= true*/ )
{
m_vAudioOrigin = ( ( bDelayedPos ) ? ( m_vDelayedPosition ) : ( m_vOldPosition ) );
CEffectData fxData;
fxData.m_vAngles = ( ( bDelayedPos ) ? ( m_qDelayedAngles ) : ( m_qOldAngles ) );
Vector vForward, vUp;
AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
fxData.m_vOrigin = m_vAudioOrigin + vForward * 1.0f;
fxData.m_nColor = ( ( m_bIsPortal2 ) ? ( 1 ) : ( 0 ) );
EmitSound_t ep;
CPASAttenuationFilter filter( m_vDelayedPosition );
ep.m_nChannel = CHAN_STATIC;
ep.m_flVolume = 1.0f;
ep.m_pOrigin = &m_vAudioOrigin;
int nTeam = GetTeamNumber();
int nPortalNum = m_bIsPortal2 ? 2 : 1;
// Rumble effects on the firing player (if one exists)
CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
CBasePlayer* pPlayer = NULL;
if ( pPortalGun )
{
pPlayer = (CBasePlayer*)pPortalGun->GetOwner();
if ( pPlayer )
{
if ( iEffect != PORTAL_FIZZLE_CLOSE &&
iEffect != PORTAL_FIZZLE_SUCCESS &&
iEffect != PORTAL_FIZZLE_NONE )
{
pPlayer->RumbleEffect( RUMBLE_PORTAL_PLACEMENT_FAILURE, 0, RUMBLE_FLAGS_NONE );
}
nTeam = pPlayer->GetTeamNumber();
}
}
// Pick a fizzle effect
switch ( iEffect )
{
case PORTAL_FIZZLE_CANT_FIT:
//DispatchEffect( "PortalFizzleCantFit", fxData );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
VectorAngles( vUp, vForward, fxData.m_vAngles );
CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
break;
case PORTAL_FIZZLE_OVERLAPPED_LINKED:
{
/*CProp_Portal *pLinkedPortal = m_hLinkedPortal;
if ( pLinkedPortal )
{
Vector vLinkedForward;
pLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
fxData.m_vStart = pLink3edPortal->GetAbsOrigin() + vLinkedForward * 5.0f;
}*/
//DispatchEffect( "PortalFizzleOverlappedLinked", fxData );
VectorAngles( vUp, vForward, fxData.m_vAngles );
CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
break;
}
case PORTAL_FIZZLE_BAD_VOLUME:
//DispatchEffect( "PortalFizzleBadVolume", fxData );
VectorAngles( vUp, vForward, fxData.m_vAngles );
CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
break;
case PORTAL_FIZZLE_BAD_SURFACE:
//DispatchEffect( "PortalFizzleBadSurface", fxData );
VectorAngles( vUp, vForward, fxData.m_vAngles );
CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
break;
case PORTAL_FIZZLE_KILLED:
//DispatchEffect( "PortalFizzleKilled", fxData );
VectorAngles( vUp, vForward, fxData.m_vAngles );
CreatePortalEffect( pPlayer, PORTAL_FIZZLE_CLOSE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
ep.m_pSoundName = "Portal.fizzle_moved";
break;
case PORTAL_FIZZLE_CLEANSER:
//DispatchEffect( "PortalFizzleCleanser", fxData );
VectorAngles( vUp, vForward, fxData.m_vAngles );
CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
break;
case PORTAL_FIZZLE_CLOSE:
//DispatchEffect( "PortalFizzleKilled", fxData );
VectorAngles( vUp, vForward, fxData.m_vAngles );
CreatePortalEffect( pPlayer, PORTAL_FIZZLE_CLOSE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
ep.m_pSoundName = ( ( m_bIsPortal2 ) ? ( "Portal.close_red" ) : ( "Portal.close_blue" ) );
break;
case PORTAL_FIZZLE_NEAR_BLUE:
{
if ( !m_bIsPortal2 )
{
Vector vLinkedForward;
m_hLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
fxData.m_vOrigin = m_hLinkedPortal->GetAbsOrigin() + vLinkedForward * 16.0f;
fxData.m_vAngles = m_hLinkedPortal->GetAbsAngles();
}
else
{
GetVectors( &vForward, NULL, NULL );
fxData.m_vOrigin = GetAbsOrigin() + vForward * 16.0f;
fxData.m_vAngles = GetAbsAngles();
}
//DispatchEffect( "PortalFizzleNear", fxData );
AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
VectorAngles( vUp, vForward, fxData.m_vAngles );
CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
break;
}
case PORTAL_FIZZLE_NEAR_RED:
{
if ( m_bIsPortal2 )
{
Vector vLinkedForward;
m_hLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
fxData.m_vOrigin = m_hLinkedPortal->GetAbsOrigin() + vLinkedForward * 16.0f;
fxData.m_vAngles = m_hLinkedPortal->GetAbsAngles();
}
else
{
GetVectors( &vForward, NULL, NULL );
fxData.m_vOrigin = GetAbsOrigin() + vForward * 16.0f;
fxData.m_vAngles = GetAbsAngles();
}
//DispatchEffect( "PortalFizzleNear", fxData );
AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
VectorAngles( vUp, vForward, fxData.m_vAngles );
CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
ep.m_pSoundName = "Portal.fizzle_invalid_surface";
break;
}
case PORTAL_FIZZLE_SUCCESS:
VectorAngles( vUp, vForward, fxData.m_vAngles );
CreatePortalEffect( pPlayer, PORTAL_FIZZLE_SUCCESS, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
// Don't make a sound!
return;
case PORTAL_FIZZLE_NONE:
// Don't do anything!
return;
}
EmitSound( filter, SOUND_FROM_WORLD, ep );
}
//-----------------------------------------------------------------------------
// Purpose: Create the portal effect
//-----------------------------------------------------------------------------
void CProp_Portal::CreatePortalEffect( CBasePlayer* pPlayer, int iEffect, Vector vecOrigin, QAngle qAngles, int nTeam, int nPortalNum )
{
if ( !pPlayer || iEffect == PORTAL_FIZZLE_NONE )
return;
CBroadcastRecipientFilter filter;
filter.MakeReliable();
// remove the player who shot it because we handle this in
// the client code and don't need to send a message
if ( pPlayer->m_bPredictionEnabled )
{
filter.RemoveRecipient( pPlayer );
}
UserMessageBegin( filter, "PortalFX_Surface" );
WRITE_SHORT( entindex() );
WRITE_SHORT( pPlayer->entindex() );
WRITE_BYTE( nTeam );
WRITE_BYTE( nPortalNum );
WRITE_BYTE( iEffect );
WRITE_VEC3COORD( vecOrigin );
WRITE_ANGLES( qAngles );
MessageEnd();
}
//-----------------------------------------------------------------------------
// Purpose: Fizzle the portal
//-----------------------------------------------------------------------------
void CProp_Portal::OnPortalDeactivated( void )
{
if ( m_pAmbientSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundChangeVolume( m_pAmbientSound, 0.0, 0.0 );
}
//TODO: Fizzle Effects
DoFizzleEffect( m_FizzleEffect );
m_FizzleEffect = PORTAL_FIZZLE_KILLED; //assume we want a generic killed type unless someone sets it to something else before we fizzle next. Lets CPortal_Base2D kill us with a fizzle effect while it has no knowledge of fizzling
BaseClass::OnPortalDeactivated();
}
//-----------------------------------------------------------------------------
// Purpose: Portal will fizzle next time we get to think
//-----------------------------------------------------------------------------
void CProp_Portal::Fizzle( void )
{
m_FizzleEffect = PORTAL_FIZZLE_NONE; //Logic that uses Fizzle() always calls DoFizzleEffect() manually
//DeactivatePortalOnThink();
DeactivatePortalNow();
}
void CProp_Portal::Activate( void )
{
CreateSounds();
BaseClass::Activate();
}
//-----------------------------------------------------------------------------
// Purpose: Kinda sucks... Normal triggers won't find portals because they're also triggers.
// Rather than addressing that directly, portal detectors look for portals with an explicit OBB check.
//
//-----------------------------------------------------------------------------
void CProp_Portal::UpdatePortalDetectorsOnPortalMoved( void )
{
for ( CFuncPortalDetector *pDetector = GetPortalDetectorList(); pDetector != NULL; pDetector = pDetector->m_pNext )
{
pDetector->UpdateOnPortalMoved( this );
}
}
void CProp_Portal::UpdatePortalDetectorsOnPortalActivated( void )
{
for ( CFuncPortalDetector *pDetector = GetPortalDetectorList(); pDetector != NULL; pDetector = pDetector->m_pNext )
{
pDetector->UpdateOnPortalActivated( this );
}
}
void CProp_Portal::UpdatePortalLinkage( void )
{
if( IsActive() )
{
CProp_Portal *pLink = (CProp_Portal *)m_hLinkedPortal.Get();
if( !(pLink && pLink->IsActive()) )
{
//no old link, or inactive old link
if( pLink )
{
//we had an old link, must be inactive. Make doubly sure it's disconnected
if( pLink->m_hLinkedPortal.Get() != NULL )
{
if( pLink->m_hLinkedPortal.Get() == this )
pLink->m_hLinkedPortal = NULL; //avoid recursion
pLink->UpdatePortalLinkage();
}
pLink = NULL;
}
int iPortalCount = s_PortalLinkageGroups[m_iLinkageGroupID].Count();
// More than two sharing a linkage id? is that valid?
//Assert( iPortalCount <3 ); yes it is as long as only two are active
if( iPortalCount != 0 )
{
CProp_Portal **pPortals = s_PortalLinkageGroups[m_iLinkageGroupID].Base();
for( int i = 0; i != iPortalCount; ++i )
{
CProp_Portal *pCurrentPortal = pPortals[i];
if( pCurrentPortal == this )
continue;
if( pCurrentPortal->IsActive() &&
(pCurrentPortal->m_hLinkedPortal.Get() == NULL) &&
(pCurrentPortal->m_fNetworkHalfWidth == m_fNetworkHalfWidth) &&
(pCurrentPortal->m_fNetworkHalfHeight == m_fNetworkHalfHeight) )
{
pLink = pCurrentPortal;
pCurrentPortal->m_hLinkedPortal = this;
pCurrentPortal->UpdatePortalLinkage();
break;
}
}
}
}
m_hLinkedPortal = pLink;
}
else
{
CProp_Portal *pRemote = (CProp_Portal *)m_hLinkedPortal.Get();
//apparently we've been deactivated
m_PortalSimulator.DetachFromLinked();
m_PortalSimulator.ReleaseAllEntityOwnership();
m_hLinkedPortal = NULL;
if( pRemote )
pRemote->UpdatePortalLinkage();
}
BaseClass::UpdatePortalLinkage();
}
void CProp_Portal::DispatchPortalPlacementParticles( bool bIsSecondaryPortal )
{
// never do this in multiplayer
if ( GameRules()->IsMultiplayer() )
return;
// the particle effects are no longer created on the server in SP unless this convar is set,
// if it's not set, they are created on the client in function: CreateAttachedParticles
if ( !use_server_portal_particles.GetBool() )
return;
// Send the particles only to the player who
CBasePlayer *pFiringPlayer = ToBasePlayer( m_hFiredByPlayer.Get() );
if ( pFiringPlayer )
{
CSingleUserRecipientFilter localFilter( pFiringPlayer );
localFilter.MakeReliable();
DispatchParticleEffect( ( ( bIsSecondaryPortal ) ? ( "portal_2_edge" ) : ( "portal_1_edge" ) ), PATTACH_POINT_FOLLOW, this, "particles", true, -1, &localFilter );
}
}
void CProp_Portal::NewLocation( const Vector &vOrigin, const QAngle &qAngles )
{
BaseClass::NewLocation( vOrigin, qAngles );
CreateSounds();
UpdatePortalDetectorsOnPortalMoved();
if( (m_hLinkedPortal.Get() != NULL) && (m_bOldActivatedState == false) && (IsActive() == true) )
{
//went from inactive to active
UpdatePortalDetectorsOnPortalActivated();
((CProp_Portal *)m_hLinkedPortal.Get())->UpdatePortalDetectorsOnPortalActivated();
}
if( m_NotifyOnPortalled && !m_NotifyOnPortalled->IsPortalTouchingDetector( this ) )
m_NotifyOnPortalled = NULL;
if ( m_pAmbientSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundChangeVolume( m_pAmbientSound, 0.4, 0.1 );
}
// Place the particles in position
DispatchPortalPlacementParticles( m_bIsPortal2 );
if( !IsMobile() )
{
if ( m_bIsPortal2 )
{
EmitSound( "Portal.open_red" );
}
else
{
EmitSound( "Portal.open_blue" );
}
}
}
void CProp_Portal::PreTeleportTouchingEntity( CBaseEntity *pOther )
{
if( m_NotifyOnPortalled )
m_NotifyOnPortalled->OnPrePortalled( pOther, true );
CProp_Portal *pLinked = (CProp_Portal *)m_hLinkedPortal.Get();
if( pLinked->m_NotifyOnPortalled )
pLinked->m_NotifyOnPortalled->OnPrePortalled( pOther, false );
BaseClass::PreTeleportTouchingEntity( pOther );
}
void CProp_Portal::PostTeleportTouchingEntity( CBaseEntity *pOther )
{
if( m_NotifyOnPortalled )
m_NotifyOnPortalled->OnPostPortalled( pOther, true );
CProp_Portal *pLinked = (CProp_Portal *)m_hLinkedPortal.Get();
if( pLinked->m_NotifyOnPortalled )
pLinked->m_NotifyOnPortalled->OnPostPortalled( pOther, false );
BaseClass::PostTeleportTouchingEntity( pOther );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CProp_Portal::ActivatePortal( void )
{
m_hPlacedBy = NULL;
Vector vOrigin;
vOrigin = GetAbsOrigin();
Vector vForward, vUp;
GetVectors( &vForward, 0, &vUp );
CTraceFilterSimpleClassnameList baseFilter( this, COLLISION_GROUP_NONE );
UTIL_Portal_Trace_Filter( &baseFilter );
CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
trace_t tr;
UTIL_TraceLine( vOrigin + vForward, vOrigin + vForward * -8.0f, MASK_SHOT_PORTAL, &traceFilterPortalShot, &tr );
QAngle qAngles;
VectorAngles( tr.plane.normal, vUp, qAngles );
PortalPlacementResult_t eResult = VerifyPortalPlacementAndFizzleBlockingPortals( this, tr.endpos, qAngles, GetHalfWidth(), GetHalfHeight(), PORTAL_PLACED_BY_FIXED );
PlacePortal( tr.endpos, qAngles, eResult );
// If the fixed portal is overlapping a portal that was placed before it... kill it!
if ( PortalPlacementSucceeded( eResult ) )
{
CreateSounds();
if ( m_pAmbientSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundChangeVolume( m_pAmbientSound, 0.4, 0.1 );
}
// Place the particles in position
DispatchPortalPlacementParticles( m_bIsPortal2 );
if ( m_bIsPortal2 )
{
EmitSound( "Portal.open_red" );
}
else
{
EmitSound( "Portal.open_blue" );
}
}
UpdatePortalTeleportMatrix();
UpdatePortalLinkage();
CBaseProjector::TestAllForProjectionChanges();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CProp_Portal::DeactivatePortal( void )
{
if ( m_pAmbientSound )
{
CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
controller.SoundChangeVolume( m_pAmbientSound, 0.0, 0.0 );
}
StopParticleEffects( this );
UpdatePortalTeleportMatrix();
UpdatePortalLinkage();
CBaseProjector::TestAllForProjectionChanges();
}
void CProp_Portal::InputSetActivatedState( inputdata_t &inputdata )
{
SetActive( inputdata.value.Bool() );
if ( IsActive() )
{
ActivatePortal();
}
else
{
DeactivatePortal();
}
}
void CProp_Portal::InputFizzle( inputdata_t &inputdata )
{
DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
Fizzle();
}
//-----------------------------------------------------------------------------
// Purpose: Map can call new location, so far it's only for debugging purposes so it's not made to be very robust.
// Input : &inputdata - String with 6 float entries with space delimiters, location and orientation
//-----------------------------------------------------------------------------
void CProp_Portal::InputNewLocation( inputdata_t &inputdata )
{
char sLocationStats[MAX_PATH];
Q_strncpy( sLocationStats, inputdata.value.String(), sizeof(sLocationStats) );
// first 3 are location of new origin
Vector vNewOrigin;
char* pTok = strtok( sLocationStats, " " );
vNewOrigin.x = atof(pTok);
pTok = strtok( NULL, " " );
vNewOrigin.y = atof(pTok);
pTok = strtok( NULL, " " );
vNewOrigin.z = atof(pTok);
// Next 3 entries are new angles
QAngle vNewAngles;
pTok = strtok( NULL, " " );
vNewAngles.x = atof(pTok);
pTok = strtok( NULL, " " );
vNewAngles.y = atof(pTok);
pTok = strtok( NULL, " " );
vNewAngles.z = atof(pTok);
// Call main placement function (skipping placement rules)
NewLocation( vNewOrigin, vNewAngles );
}
void CProp_Portal::InputResize( inputdata_t &inputdata )
{
char sResizeStats[MAX_PATH];
Q_strncpy( sResizeStats, inputdata.value.String(), sizeof(sResizeStats) );
char* pTok = strtok( sResizeStats, " " );
float fHalfWidth = atof(pTok);
pTok = strtok( NULL, " " );
float fHalfHeight = atof(pTok);
Resize( fHalfWidth, fHalfHeight );
}
void CProp_Portal::InputSetLinkageGroupId( inputdata_t &inputdata )
{
int iGroupId = inputdata.value.Int();
if ( ( iGroupId >= 0 ) && ( iGroupId < 255 ) )
{
ChangeLinkageGroup( iGroupId );
if ( IsActive() )
{
SetActive( false );
// shut the portal down and reactivate it so it will re-link with new portal group id
DeactivatePortal();
SetActive( true );
ActivatePortal();
}
}
else
{
Warning( "*** SetLinkageGroupId input failed because Portal ID must be between 0 and 255!\n" );
}
}
void CProp_Portal::AddToLinkageGroup( void )
{
if ( m_iLinkageGroupID != PORTAL_LINKAGE_GROUP_INVALID )
{
if( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) == -1 )
s_PortalLinkageGroups[m_iLinkageGroupID].AddToTail( this );
}
}
void CProp_Portal::ChangeLinkageGroup( unsigned char iLinkageGroupID )
{
if ( iLinkageGroupID == PORTAL_LINKAGE_GROUP_INVALID )
{
// invalid is the 'inactive portal' group for portals not yet linked.
m_iLinkageGroupID = iLinkageGroupID;
return;
}
// We should be moving from a linkage id to another one, unles we're coming from INVALID
Assert( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) != -1 || m_iLinkageGroupID == PORTAL_LINKAGE_GROUP_INVALID );
s_PortalLinkageGroups[m_iLinkageGroupID].FindAndRemove( this );
s_PortalLinkageGroups[iLinkageGroupID].AddToTail( this );
m_iLinkageGroupID = iLinkageGroupID;
}
CProp_Portal *CProp_Portal::FindPortal( unsigned char iLinkageGroupID, bool bPortal2, bool bCreateIfNothingFound /*= false*/ )
{
int iPortalCount = s_PortalLinkageGroups[iLinkageGroupID].Count();
if( iPortalCount != 0 )
{
CProp_Portal *pFoundInactive = NULL;
CProp_Portal **pPortals = s_PortalLinkageGroups[iLinkageGroupID].Base();
for( int i = 0; i != iPortalCount; ++i )
{
if( pPortals[i]->m_bIsPortal2 == bPortal2 )
{
if( pPortals[i]->IsActive() )
return pPortals[i];
else
pFoundInactive = pPortals[i];
}
}
if( pFoundInactive )
return pFoundInactive;
}
if( bCreateIfNothingFound )
{
CProp_Portal *pPortal = (CProp_Portal *)CreateEntityByName( "prop_portal" );
pPortal->m_iLinkageGroupID = iLinkageGroupID;
pPortal->m_bIsPortal2 = bPortal2;
DispatchSpawn( pPortal );
return pPortal;
}
return NULL;
}
const CUtlVector<CProp_Portal *> *CProp_Portal::GetPortalLinkageGroup( unsigned char iLinkageGroupID )
{
return &s_PortalLinkageGroups[iLinkageGroupID];
}
// Hands out linkage IDs in order. If somebody has taken the slot, it walks to a free one and picks that as the new starting location.
static unsigned char s_iBestGuessUnusedLinkageID = 0;
unsigned char UTIL_GetUnusedLinkageID( void )
{
if ( s_PortalLinkageGroups[s_iBestGuessUnusedLinkageID].Count() == 0 )
{
// early out for best guess
return s_iBestGuessUnusedLinkageID++;
}
else
{
// walk all linkage groups for a free one
for ( int i = 0; i < 256; ++i )
{
if ( s_PortalLinkageGroups[i].Count() == 0 )
{
s_iBestGuessUnusedLinkageID = i+1;
return i;
}
}
}
Warning( "*** All portal linkage IDs in use! ***\nThere may be >254 portal pairs, or some bug causing the linkage IDs not to be freed up.\n" );
Assert( 0 );
return PORTAL_LINKAGE_GROUP_INVALID;
}
//------------------------------------------------------------------------------
// Purpose: Create an NPC of the given type
//------------------------------------------------------------------------------
void CC_Resize_Portals( const CCommand &args )
{
if( args.ArgC() < 3 )
{
Warning( "syntax: Portals_ResizeAll [half width] [half height]\n" );
return;
}
float fHalfWidth = atof(args[1]);
float fHalfHeight = atof(args[2]);
int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
for( int i = 0; i != iPortalCount; ++i )
{
CProp_Portal_Shared::AllPortals[i]->Resize( fHalfWidth, fHalfHeight );
}
CProp_Portal::ms_DefaultPortalHalfWidth = fHalfWidth;
CProp_Portal::ms_DefaultPortalHalfHeight = fHalfHeight;
}
static ConCommand Portals_ResizeAll("Portals_ResizeAll", CC_Resize_Portals, "Resizes all portals (for testing), Portals_ResizeAll [half width] [half height]", FCVAR_CHEAT);

View File

@@ -0,0 +1,122 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#ifndef PROP_PORTAL_H
#define PROP_PORTAL_H
#ifdef _WIN32
#pragma once
#endif
#include "baseanimating.h"
#include "PortalSimulation.h"
#include "portal_base2d.h"
#include "../portal2/func_portalled.h"
// FIX ME
#include "portal_shareddefs.h"
// This is for portals not yet linked.
#define PORTAL_LINKAGE_GROUP_INVALID 255
class CPhysicsCloneArea;
class CProp_Portal : public CPortal_Base2D
{
public:
DECLARE_CLASS( CProp_Portal, CPortal_Base2D );
DECLARE_SERVERCLASS();
DECLARE_DATADESC();
CProp_Portal( void );
virtual ~CProp_Portal( void );
CSoundPatch *m_pAmbientSound;
virtual void Precache( void );
virtual void CreateSounds( void );
virtual void StopLoopingSounds( void );
virtual void Spawn( void );
virtual void Activate( void );
virtual void OnRestore( void );
void DelayedPlacementThink( void );
static const char * s_szDelayedPlacementThinkContext;
void PlacePortal( const Vector &vOrigin, const QAngle &qAngles, PortalPlacementResult_t eResult, bool bDelay = false );
void NewLocation( const Vector &vOrigin, const QAngle &qAngles );
virtual void PreTeleportTouchingEntity( CBaseEntity *pOther );
virtual void PostTeleportTouchingEntity( CBaseEntity *pOther );
void ResetModel( void ); //sets the model and bounding box
void DoFizzleEffect( int iEffect, bool bDelayedPos = true ); //display cool visual effect
void CreatePortalEffect( CBasePlayer* pPlayer, int iEffect, Vector vecOrigin, QAngle qAngles, int nTeam, int nPortalNum );
void OnPortalDeactivated( void );
void Fizzle( void );
void ActivatePortal( void );
void DeactivatePortal( void );
void InputSetActivatedState( inputdata_t &inputdata );
void InputFizzle( inputdata_t &inputdata );
void InputNewLocation( inputdata_t &inputdata );
void InputResize( inputdata_t &inputdata );
void InputSetLinkageGroupId( inputdata_t &inputdata );
void AddToLinkageGroup( void );
virtual void UpdatePortalLinkage( void );
virtual void StartTouch( CBaseEntity *pOther );
virtual void Touch( CBaseEntity *pOther );
virtual void EndTouch( CBaseEntity *pOther );
//virtual int ShouldTransmit( const CCheckTransmitInfo *pInfo ) { return FL_EDICT_ALWAYS; }
virtual int UpdateTransmitState( void ) // set transmit filter to transmit always
{
return SetTransmitState( FL_EDICT_ALWAYS );
}
private:
void DispatchPortalPlacementParticles( bool bIsSecondaryPortal );
void UpdatePortalDetectorsOnPortalMoved( void );
void UpdatePortalDetectorsOnPortalActivated( void );
unsigned char m_iLinkageGroupID; //a group ID specifying which portals this one can possibly link to
PortalFizzleType_t m_FizzleEffect;
CNetworkHandle( CBaseEntity, m_hFiredByPlayer );
CHandle<CFunc_Portalled> m_NotifyOnPortalled; //an entity that forwards notifications of teleports to map logic entities
public:
friend class CPropPortalTunnel;
inline unsigned char GetLinkageGroup( void ) const { return m_iLinkageGroupID; };
void ChangeLinkageGroup( unsigned char iLinkageGroupID );
void SetFiredByPlayer( CBasePlayer *pPlayer );
inline CBasePlayer *GetFiredByPlayer( void ) const { return (CBasePlayer *)m_hFiredByPlayer.Get(); }
inline void SetFuncPortalled( CFunc_Portalled *pPortalledEnt = NULL ) { m_NotifyOnPortalled = pPortalledEnt; }
static bool ms_DefaultPortalSizeInitialized; // for CEG protection
static float ms_DefaultPortalHalfWidth;
static float ms_DefaultPortalHalfHeight;
//NULL portal will return default width/height
static void GetPortalSize( float &fHalfWidth, float &fHalfHeight, CProp_Portal *pPortal = NULL );
//find a portal with the designated attributes, or creates one with them, favors active portals over inactive
static CProp_Portal *FindPortal( unsigned char iLinkageGroupID, bool bPortal2, bool bCreateIfNothingFound = false );
static const CUtlVector<CProp_Portal *> *GetPortalLinkageGroup( unsigned char iLinkageGroupID );
virtual float GetMinimumExitSpeed( bool bPlayer, bool bEntranceOnFloor, bool bExitOnFloor, const Vector &vEntityCenterAtExit, CBaseEntity *pEntity ); //return -FLT_MAX for no minimum
virtual float GetMaximumExitSpeed( bool bPlayer, bool bEntranceOnFloor, bool bExitOnFloor, const Vector &vEntityCenterAtExit, CBaseEntity *pEntity ); //return FLT_MAX for no maximum
CNetworkVar( int, m_nPlacementAttemptParity ); //Increments every time we try to move the portal in a predictable way. Will send a network packet to catch cases where placement succeeds on the client, but fails on the server.
};
// Finds a free linkage id for a portal.
unsigned char UTIL_GetUnusedLinkageID( void );
#endif //#ifndef PROP_PORTAL_H

View File

@@ -0,0 +1,158 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#include "cbase.h"
#include "pvs_extender.h"
#include "util_shared.h"
#include "tier1/utlvector.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
static CUtlVector<CPVS_Extender *> s_AllExtenders;
CPVS_Extender::CPVS_Extender( void )
{
s_AllExtenders.AddToTail( this );
}
CPVS_Extender::~CPVS_Extender( void )
{
s_AllExtenders.FindAndFastRemove( this );
}
void CPVS_Extender::ComputeExtendedPVS( const CBaseEntity *pViewEntity, const Vector &vVisOrigin, unsigned char *outputPVS, int pvssize, int iMaxRecursions )
{
int iAllExtenderCount = s_AllExtenders.Count();
if( iAllExtenderCount == 0 )
return;
static CThreadFastMutex s_PVSExtenderMutex;
AUTO_LOCK_FM( s_PVSExtenderMutex );
CPVS_Extender **pAllExtenders = s_AllExtenders.Base();
int iExtenderCount = 0;
CPVS_Extender **pExtenders = (CPVS_Extender **)stackalloc( sizeof( CPVS_Extender * ) * iAllExtenderCount );
//filter out portals that can't possibly extend visibility up-front. So we don't have to do so in our recursions
for( int i = 0; i != iAllExtenderCount; ++i )
{
CPVS_Extender *pExtender = pAllExtenders[i];
Assert( pExtender );
if ( pExtender->IsExtenderValid() )
{
if ( pExtender->GetExtenderNetworkProp()->AreaNum() < 0 )
{
Assert( false );
continue;
}
pExtenders[iExtenderCount] = pExtender;
++iExtenderCount;
}
}
if( iExtenderCount == 0 )
return;
int iAreasNetworked[MAX_MAP_AREAS];
iAreasNetworked[0] = pViewEntity->NetworkProp()->AreaNum(); //safe assumption?
for( int i = 1; i != MAX_MAP_AREAS; ++i )
{
iAreasNetworked[i] = -1;
}
//unsigned char *viewEntPVS = (unsigned char *)stackalloc( sizeof( unsigned char ) * pvssize );
//memcpy( viewEntPVS, outputPVS, sizeof( unsigned char ) * pvssize );
unsigned char viewEntPVS[MAX_MAP_LEAFS/8];
engine->GetPVSForCluster( engine->GetClusterForOrigin( vVisOrigin ), sizeof( viewEntPVS ), viewEntPVS );
//do we OR that into the output PVS?
//grab the local pvs of every extender up front to avoid repeating it in recursions
//unsigned char *pExtenderPVSs = (unsigned char *)stackalloc( sizeof( unsigned char ) * (MAX_MAP_LEAFS/8) * iPortalCount );
ExtenderInstanceData_t *pExtenderData = (ExtenderInstanceData_t *)stackalloc( sizeof( ExtenderInstanceData_t ) * iExtenderCount );
for( int i = 0; i != iExtenderCount; ++i )
{
pExtenders[i]->m_pExtenderData = &pExtenderData[i];
engine->GetPVSForCluster( engine->GetClusterForOrigin( pExtenders[i]->GetExtensionPVSOrigin() ), sizeof( unsigned char ) * (MAX_MAP_LEAFS/8), pExtenderData[i].iPVSBits );
pExtenders[i]->m_pExtenderData->bAddedToPVSAlready = false;
}
VisExtensionChain_t chainRoot;
chainRoot.m_nArea = iAreasNetworked[0];
chainRoot.pParentChain = NULL;
const edict_t *viewEdict = pViewEntity->edict();
for( int i = 0; i != iExtenderCount; ++i )
{
CPVS_Extender *pExtender = pExtenders[i];
if ( pExtender->GetExtenderEdict() == viewEdict )
continue;
if ( pExtender->GetExtenderNetworkProp()->IsInPVS( viewEdict, viewEntPVS, pvssize ) ) //test against pViewEntity PVS, not aggregate PVS
{
chainRoot.pExtender = pExtender;
pExtender->ComputeSubVisibility( pExtenders, iExtenderCount, outputPVS, pvssize, vVisOrigin, NULL, 0, &chainRoot, iAreasNetworked, iMaxRecursions );
}
}
for( int i = 0; i != iExtenderCount; ++i )
{
pExtenders[i]->m_pExtenderData = NULL;
}
}
//does the grunt work of checking if the data has already been added, and if not, adding it
/*void CPVS_Extender::AddToPVS( unsigned char *outputPVS, int pvssize, int iAreasNetworked[MAX_MAP_AREAS] )
{
if( !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;
}
}
if( !bFound )
return;
AddPortalCornersToEnginePVS( pLinkedPortal );
m_pExtenderData->bAddedToPVSAlready = true;
}
}*/

View File

@@ -0,0 +1,52 @@
//===== Copyright © 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $NoKeywords: $
//===========================================================================//
#ifndef PVS_EXTENDER_H
#define PVS_EXTENDER_H
#ifdef _WIN32
#pragma once
#endif
class CPVS_Extender
{
public:
CPVS_Extender( void ); //self-registration
virtual ~CPVS_Extender( void ); //self-unregistration
struct VisExtensionChain_t
{
VisExtensionChain_t *pParentChain;
int m_nArea;
CPVS_Extender *pExtender;
};
virtual CServerNetworkProperty *GetExtenderNetworkProp( void )= 0;
virtual const edict_t *GetExtenderEdict( void ) const = 0;
virtual bool IsExtenderValid( void ) = 0;
virtual Vector GetExtensionPVSOrigin( void ) = 0;
//given an entity and initial pvs. Extend that pvs through any visible portals
static void ComputeExtendedPVS( const CBaseEntity *pViewEntity, const Vector &vVisOrigin, unsigned char *outputPVS, int pvssize, int iMaxRecursions );
//This extender is decidedly visible, recursively extend the visibility problem
virtual void 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 ) = 0;
//cached data to make the algorithms a bit simpler in recursions
struct ExtenderInstanceData_t
{
unsigned char iPVSBits[MAX_MAP_LEAFS/8];
bool bAddedToPVSAlready; //no need to add our data to the PVS again, but should still recurse if we alter the frustum at all
};
protected:
ExtenderInstanceData_t *m_pExtenderData;
};
#endif //#ifndef PVS_EXTENDER_H