source-engine/game/server/physobj.cpp

2144 lines
62 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "player.h"
#include "vphysics_interface.h"
#include "physics.h"
#include "vcollide_parse.h"
#include "entitylist.h"
#include "physobj.h"
#include "hierarchy.h"
#include "game.h"
#include "ndebugoverlay.h"
#include "engine/IEngineSound.h"
#include "model_types.h"
#include "props.h"
#include "physics_saverestore.h"
#include "saverestore_utlvector.h"
#include "vphysics/constraints.h"
#include "collisionutils.h"
#include "decals.h"
#include "bone_setup.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
ConVar debug_physimpact("debug_physimpact", "0" );
const char *GetMassEquivalent(float flMass);
// This is a physically simulated spring, used to join objects together and create spring forces
//
// NOTE: Springs are not physical in the sense that they only create force, they do not collide with
// anything or have any REAL constraints. They can be stretched infinitely (though this will create
// and infinite force), they can penetrate any other object (or spring). They do not occupy any space.
//
#define SF_SPRING_ONLYSTRETCH 0x0001
class CPhysicsSpring : public CBaseEntity
{
DECLARE_CLASS( CPhysicsSpring, CBaseEntity );
public:
CPhysicsSpring();
~CPhysicsSpring();
void Spawn( void );
void Activate( void );
// Inputs
void InputSetSpringConstant( inputdata_t &inputdata );
void InputSetSpringDamping( inputdata_t &inputdata );
void InputSetSpringLength( inputdata_t &inputdata );
// Debug
int DrawDebugTextOverlays(void);
void DrawDebugGeometryOverlays(void);
void GetSpringObjectConnections( string_t nameStart, string_t nameEnd, IPhysicsObject **pStart, IPhysicsObject **pEnd );
void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params );
IPhysicsObject *GetStartObject() { return m_pSpring ? m_pSpring->GetStartObject() : NULL; }
IPhysicsObject *GetEndObject() { return m_pSpring ? m_pSpring->GetEndObject() : NULL; }
DECLARE_DATADESC();
private:
IPhysicsSpring *m_pSpring;
bool m_isLocal;
// These are "template" values used to construct the spring. After creation, they are not needed
float m_tempConstant;
float m_tempLength; // This is the "ideal" length of the spring, not the length it is currently stretched to.
float m_tempDamping;
float m_tempRelativeDamping;
string_t m_nameAttachStart;
string_t m_nameAttachEnd;
Vector m_start;
Vector m_end;
unsigned int m_teleportTick;
};
LINK_ENTITY_TO_CLASS( phys_spring, CPhysicsSpring );
BEGIN_DATADESC( CPhysicsSpring )
DEFINE_PHYSPTR( m_pSpring ),
DEFINE_KEYFIELD( m_tempConstant, FIELD_FLOAT, "constant" ),
DEFINE_KEYFIELD( m_tempLength, FIELD_FLOAT, "length" ),
DEFINE_KEYFIELD( m_tempDamping, FIELD_FLOAT, "damping" ),
DEFINE_KEYFIELD( m_tempRelativeDamping, FIELD_FLOAT, "relativedamping" ),
DEFINE_KEYFIELD( m_nameAttachStart, FIELD_STRING, "attach1" ),
DEFINE_KEYFIELD( m_nameAttachEnd, FIELD_STRING, "attach2" ),
DEFINE_FIELD( m_start, FIELD_POSITION_VECTOR ),
DEFINE_KEYFIELD( m_end, FIELD_POSITION_VECTOR, "springaxis" ),
DEFINE_FIELD( m_isLocal, FIELD_BOOLEAN ),
// Not necessary to save... it's only there to make sure
// DEFINE_FIELD( m_teleportTick, FIELD_INTEGER ),
// Inputs
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpringConstant", InputSetSpringConstant ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpringLength", InputSetSpringLength ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpringDamping", InputSetSpringDamping ),
END_DATADESC()
// debug function - slow, uses dynamic_cast<> - use this to query the attached objects
// physics_debug_entity toggles the constraint system for an object using this
bool GetSpringAttachments( CBaseEntity *pEntity, CBaseEntity *pAttachOut[2], IPhysicsObject *pAttachVPhysics[2] )
{
CPhysicsSpring *pSpringEntity = dynamic_cast<CPhysicsSpring *>(pEntity);
if ( pSpringEntity )
{
IPhysicsObject *pRef = pSpringEntity->GetStartObject();
pAttachOut[0] = pRef ? static_cast<CBaseEntity *>(pRef->GetGameData()) : NULL;
pAttachVPhysics[0] = pRef;
IPhysicsObject *pAttach = pSpringEntity->GetEndObject();
pAttachOut[1] = pAttach ? static_cast<CBaseEntity *>(pAttach->GetGameData()) : NULL;
pAttachVPhysics[1] = pAttach;
return true;
}
return false;
}
CPhysicsSpring::CPhysicsSpring( void )
{
#ifdef _DEBUG
m_start.Init();
m_end.Init();
#endif
m_pSpring = NULL;
m_tempConstant = 150;
m_tempLength = 0;
m_tempDamping = 2.0;
m_tempRelativeDamping = 0.01;
m_isLocal = false;
m_teleportTick = 0xFFFFFFFF;
}
CPhysicsSpring::~CPhysicsSpring( void )
{
if ( m_pSpring )
{
physenv->DestroySpring( m_pSpring );
}
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CPhysicsSpring::InputSetSpringConstant( inputdata_t &inputdata )
{
m_tempConstant = inputdata.value.Float();
m_pSpring->SetSpringConstant(inputdata.value.Float());
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CPhysicsSpring::InputSetSpringDamping( inputdata_t &inputdata )
{
m_tempDamping = inputdata.value.Float();
m_pSpring->SetSpringDamping(inputdata.value.Float());
}
//------------------------------------------------------------------------------
// Purpose:
//------------------------------------------------------------------------------
void CPhysicsSpring::InputSetSpringLength( inputdata_t &inputdata )
{
m_tempLength = inputdata.value.Float();
m_pSpring->SetSpringLength(inputdata.value.Float());
}
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CPhysicsSpring::DrawDebugTextOverlays(void)
{
int text_offset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT)
{
char tempstr[512];
Q_snprintf(tempstr,sizeof(tempstr),"Constant: %3.2f",m_tempConstant);
EntityText(text_offset,tempstr,0);
text_offset++;
Q_snprintf(tempstr,sizeof(tempstr),"Length: %3.2f",m_tempLength);
EntityText(text_offset,tempstr,0);
text_offset++;
Q_snprintf(tempstr,sizeof(tempstr),"Damping: %3.2f",m_tempDamping);
EntityText(text_offset,tempstr,0);
text_offset++;
}
return text_offset;
}
//-----------------------------------------------------------------------------
// Purpose: Override base class to add display of fly direction
// Input :
// Output :
//-----------------------------------------------------------------------------
void CPhysicsSpring::DrawDebugGeometryOverlays(void)
{
if ( !m_pSpring )
return;
// ------------------------------
// Draw if BBOX is on
// ------------------------------
if (m_debugOverlays & OVERLAY_BBOX_BIT)
{
Vector vStartPos;
Vector vEndPos;
m_pSpring->GetEndpoints( &vStartPos, &vEndPos );
Vector vSpringDir = vEndPos - vStartPos;
VectorNormalize(vSpringDir);
Vector vLength = vStartPos + (vSpringDir*m_tempLength);
NDebugOverlay::Line(vStartPos, vLength, 0,0,255, false, 0);
NDebugOverlay::Line(vLength, vEndPos, 255,0,0, false, 0);
}
BaseClass::DrawDebugGeometryOverlays();
}
bool PointIsNearer( IPhysicsObject *pObject1, const Vector &point1, const Vector &point2 )
{
Vector center;
pObject1->GetPosition( &center, 0 );
float dist1 = (center - point1).LengthSqr();
float dist2 = (center - point2).LengthSqr();
if ( dist1 < dist2 )
return true;
return false;
}
void CPhysicsSpring::GetSpringObjectConnections( string_t nameStart, string_t nameEnd, IPhysicsObject **pStart, IPhysicsObject **pEnd )
{
IPhysicsObject *pStartObject = FindPhysicsObjectByName( STRING(nameStart), this );
IPhysicsObject *pEndObject = FindPhysicsObjectByName( STRING(nameEnd), this );
// Assume the world for missing objects
if ( !pStartObject )
{
pStartObject = g_PhysWorldObject;
}
else if ( !pEndObject )
{
// try to sort so that the world is always the start object
pEndObject = pStartObject;
pStartObject = g_PhysWorldObject;
}
else
{
CBaseEntity *pEntity0 = (CBaseEntity *) (pStartObject->GetGameData());
if ( pEntity0 )
{
g_pNotify->AddEntity( this, pEntity0 );
}
CBaseEntity *pEntity1 = (CBaseEntity *) pEndObject->GetGameData();
if ( pEntity1 )
{
g_pNotify->AddEntity( this, pEntity1 );
}
}
*pStart = pStartObject;
*pEnd = pEndObject;
}
void CPhysicsSpring::Activate( void )
{
BaseClass::Activate();
// UNDONE: save/restore all data, and only create the spring here
if ( !m_pSpring )
{
IPhysicsObject *pStart, *pEnd;
GetSpringObjectConnections( m_nameAttachStart, m_nameAttachEnd, &pStart, &pEnd );
// Needs to connect to real, different objects
if ( (!pStart || !pEnd) || (pStart == pEnd) )
{
DevMsg("ERROR: Can't init spring %s from \"%s\" to \"%s\"\n", GetDebugName(), STRING(m_nameAttachStart), STRING(m_nameAttachEnd) );
UTIL_Remove( this );
return;
}
// if m_end is not closer to pEnd than m_start, swap
if ( !PointIsNearer( pEnd, m_end, m_start ) )
{
Vector tmpVec = m_start;
m_start = m_end;
m_end = tmpVec;
}
// create the spring
springparams_t spring;
spring.constant = m_tempConstant;
spring.damping = m_tempDamping;
spring.naturalLength = m_tempLength;
spring.relativeDamping = m_tempRelativeDamping;
spring.startPosition = m_start;
spring.endPosition = m_end;
spring.useLocalPositions = false;
spring.onlyStretch = HasSpawnFlags( SF_SPRING_ONLYSTRETCH );
m_pSpring = physenv->CreateSpring( pStart, pEnd, &spring );
}
}
void CPhysicsSpring::Spawn( void )
{
SetSolid( SOLID_NONE );
m_start = GetAbsOrigin();
if ( m_tempLength <= 0 )
{
m_tempLength = (m_end - m_start).Length();
}
}
void CPhysicsSpring::NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params )
{
// don't recurse
if ( eventType != NOTIFY_EVENT_TELEPORT || (unsigned int)gpGlobals->tickcount == m_teleportTick )
return;
m_teleportTick = gpGlobals->tickcount;
PhysTeleportConstrainedEntity( pNotify, m_pSpring->GetStartObject(), m_pSpring->GetEndObject(), params.pTeleport->prevOrigin, params.pTeleport->prevAngles, params.pTeleport->physicsRotate );
}
// ---------------------------------------------------------------------
//
// CPhysBox -- physically simulated brush
//
// ---------------------------------------------------------------------
// SendTable stuff.
IMPLEMENT_SERVERCLASS_ST(CPhysBox, DT_PhysBox)
END_SEND_TABLE()
LINK_ENTITY_TO_CLASS( func_physbox, CPhysBox );
BEGIN_DATADESC( CPhysBox )
DEFINE_FIELD( m_hCarryingPlayer, FIELD_EHANDLE ),
DEFINE_KEYFIELD( m_massScale, FIELD_FLOAT, "massScale" ),
DEFINE_KEYFIELD( m_damageType, FIELD_INTEGER, "Damagetype" ),
DEFINE_KEYFIELD( m_iszOverrideScript, FIELD_STRING, "overridescript" ),
DEFINE_KEYFIELD( m_damageToEnableMotion, FIELD_INTEGER, "damagetoenablemotion" ),
DEFINE_KEYFIELD( m_flForceToEnableMotion, FIELD_FLOAT, "forcetoenablemotion" ),
DEFINE_KEYFIELD( m_angPreferredCarryAngles, FIELD_VECTOR, "preferredcarryangles" ),
DEFINE_KEYFIELD( m_bNotSolidToWorld, FIELD_BOOLEAN, "notsolid" ),
DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ),
DEFINE_INPUTFUNC( FIELD_VOID, "Sleep", InputSleep ),
DEFINE_INPUTFUNC( FIELD_VOID, "EnableMotion", InputEnableMotion ),
DEFINE_INPUTFUNC( FIELD_VOID, "DisableMotion", InputDisableMotion ),
DEFINE_INPUTFUNC( FIELD_VOID, "ForceDrop", InputForceDrop ),
DEFINE_INPUTFUNC( FIELD_VOID, "DisableFloating", InputDisableFloating ),
// Function pointers
DEFINE_ENTITYFUNC( BreakTouch ),
// Outputs
DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ),
DEFINE_OUTPUT( m_OnAwakened, "OnAwakened" ),
DEFINE_OUTPUT( m_OnMotionEnabled, "OnMotionEnabled" ),
DEFINE_OUTPUT( m_OnPhysGunPickup, "OnPhysGunPickup" ),
DEFINE_OUTPUT( m_OnPhysGunPunt, "OnPhysGunPunt" ),
DEFINE_OUTPUT( m_OnPhysGunOnlyPickup, "OnPhysGunOnlyPickup" ),
DEFINE_OUTPUT( m_OnPhysGunDrop, "OnPhysGunDrop" ),
DEFINE_OUTPUT( m_OnPlayerUse, "OnPlayerUse" ),
END_DATADESC()
// UNDONE: Save/Restore needs to take the physics object's properties into account
// UNDONE: Acceleration, velocity, angular velocity, etc. must be preserved
// UNDONE: Many of these quantities are relative to a coordinate frame
// UNDONE: Translate when going across transitions?
// UNDONE: Build transition transformation, and transform data in save/restore for IPhysicsObject
// UNDONE: Angles are saved in the entity, but not propagated back to the IPhysicsObject on restore
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysBox::Spawn( void )
{
// Initialize damage modifiers. Must be done before baseclass spawn.
m_flDmgModBullet = func_breakdmg_bullet.GetFloat();
m_flDmgModClub = func_breakdmg_club.GetFloat();
m_flDmgModExplosive = func_breakdmg_explosive.GetFloat();
ParsePropData();
Precache();
m_iMaxHealth = ( m_iHealth > 0 ) ? m_iHealth : 1;
if ( HasSpawnFlags( SF_BREAK_TRIGGER_ONLY ) )
{
m_takedamage = DAMAGE_EVENTS_ONLY;
AddSpawnFlags( SF_BREAK_DONT_TAKE_PHYSICS_DAMAGE );
}
else if ( m_iHealth == 0 )
{
m_takedamage = DAMAGE_EVENTS_ONLY;
AddSpawnFlags( SF_BREAK_DONT_TAKE_PHYSICS_DAMAGE );
}
else
{
m_takedamage = DAMAGE_YES;
}
SetMoveType( MOVETYPE_NONE );
SetAbsVelocity( vec3_origin );
SetModel( STRING( GetModelName() ) );
SetSolid( SOLID_VPHYSICS );
if ( HasSpawnFlags( SF_PHYSBOX_DEBRIS ) )
{
SetCollisionGroup( COLLISION_GROUP_DEBRIS );
}
if ( HasSpawnFlags( SF_PHYSBOX_NO_ROTORWASH_PUSH ) )
{
AddEFlags( EFL_NO_ROTORWASH_PUSH );
}
if ( m_bNotSolidToWorld )
{
AddSolidFlags( FSOLID_NOT_SOLID );
}
CreateVPhysics();
m_hCarryingPlayer = NULL;
SetTouch( &CPhysBox::BreakTouch );
if ( HasSpawnFlags( SF_BREAK_TRIGGER_ONLY ) ) // Only break on trigger
{
SetTouch( NULL );
}
if ( m_impactEnergyScale == 0 )
{
m_impactEnergyScale = 1.0;
}
}
// shared from studiomdl, checks for long, thin objects and adds some damping
// to prevent endless rolling due to low inertia
static bool ShouldDampRotation( const CPhysCollide *pCollide )
{
Vector mins, maxs;
physcollision->CollideGetAABB( &mins, &maxs, pCollide, vec3_origin, vec3_angle );
Vector size = maxs-mins;
int largest = 0;
float largeSize = size[0];
for ( int i = 1; i < 3; i++ )
{
if ( size[i] > largeSize )
{
largeSize = size[i];
largest = i;
}
}
size[largest] = 0;
float len = size.Length();
if ( len > 0 )
{
float sizeRatio = largeSize / len;
// HACKHACK: Hardcoded size ratio to induce damping
// This prevents long skinny objects from rolling endlessly
if ( sizeRatio > 9 )
return true;
}
return false;
}
bool CPhysBox::CreateVPhysics()
{
solid_t tmpSolid;
PhysModelParseSolid( tmpSolid, this, GetModelIndex() );
if ( m_massScale > 0 )
{
tmpSolid.params.mass *= m_massScale;
}
vcollide_t *pVCollide = modelinfo->GetVCollide( GetModelIndex() );
PhysGetMassCenterOverride( this, pVCollide, tmpSolid );
PhysSolidOverride( tmpSolid, m_iszOverrideScript );
if ( tmpSolid.params.rotdamping < 1.0f && ShouldDampRotation(pVCollide->solids[0]) )
{
tmpSolid.params.rotdamping = 1.0f;
}
IPhysicsObject *pPhysics = VPhysicsInitNormal( GetSolid(), GetSolidFlags(), true, &tmpSolid );
if ( m_damageType == 1 )
{
PhysSetGameFlags( pPhysics, FVPHYSICS_DMG_SLICE );
}
// Wake it up if not asleep
if ( !HasSpawnFlags(SF_PHYSBOX_ASLEEP) )
{
pPhysics->Wake();
}
if ( HasSpawnFlags(SF_PHYSBOX_MOTIONDISABLED) || m_damageToEnableMotion > 0 || m_flForceToEnableMotion > 0 )
{
pPhysics->EnableMotion( false );
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CPhysBox::ObjectCaps()
{
int caps = BaseClass::ObjectCaps() | FCAP_WCEDIT_POSITION;
if ( HasSpawnFlags( SF_PHYSBOX_ENABLE_PICKUP_OUTPUT ) )
{
caps |= FCAP_IMPULSE_USE;
}
else if ( !HasSpawnFlags( SF_PHYSBOX_IGNOREUSE ) )
{
if ( CBasePlayer::CanPickupObject( this, 35, 128 ) )
{
caps |= FCAP_IMPULSE_USE;
}
}
return caps;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysBox::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
CBasePlayer *pPlayer = ToBasePlayer( pActivator );
if ( pPlayer )
{
if ( HasSpawnFlags( SF_PHYSBOX_ENABLE_PICKUP_OUTPUT ) )
{
m_OnPlayerUse.FireOutput( this, this );
}
if ( !HasSpawnFlags( SF_PHYSBOX_IGNOREUSE ) )
{
pPlayer->PickupObject( this );
}
}
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
bool CPhysBox::CanBePickedUpByPhyscannon()
{
if ( HasSpawnFlags( SF_PHYSBOX_NEVER_PICK_UP ) )
return false;
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if ( !pPhysicsObject )
return false;
if ( !pPhysicsObject->IsMotionEnabled() && !HasSpawnFlags( SF_PHYSBOX_ENABLE_ON_PHYSCANNON ) )
return false;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CPhysBox::DrawDebugTextOverlays(void)
{
int text_offset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT)
{
if (VPhysicsGetObject())
{
char tempstr[512];
Q_snprintf(tempstr, sizeof(tempstr),"Mass: %.2f kg / %.2f lb (%s)", VPhysicsGetObject()->GetMass(), kg2lbs(VPhysicsGetObject()->GetMass()), GetMassEquivalent(VPhysicsGetObject()->GetMass()));
EntityText( text_offset, tempstr, 0);
text_offset++;
}
}
return text_offset;
}
//-----------------------------------------------------------------------------
// Purpose: Input handler that breaks the physics object away from its parent
// and starts it simulating.
//-----------------------------------------------------------------------------
void CPhysBox::InputWake( inputdata_t &inputdata )
{
VPhysicsGetObject()->Wake();
}
//-----------------------------------------------------------------------------
// Purpose: Input handler that breaks the physics object away from its parent
// and stops it simulating.
//-----------------------------------------------------------------------------
void CPhysBox::InputSleep( inputdata_t &inputdata )
{
VPhysicsGetObject()->Sleep();
}
//-----------------------------------------------------------------------------
// Purpose: Enable physics motion and collision response (on by default)
//-----------------------------------------------------------------------------
void CPhysBox::InputEnableMotion( inputdata_t &inputdata )
{
EnableMotion();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysBox::EnableMotion( void )
{
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if ( pPhysicsObject != NULL )
{
pPhysicsObject->EnableMotion( true );
pPhysicsObject->Wake();
}
m_damageToEnableMotion = 0;
m_flForceToEnableMotion = 0;
m_OnMotionEnabled.FireOutput( this, this, 0 );
}
//-----------------------------------------------------------------------------
// Purpose: Disable any physics motion or collision response
//-----------------------------------------------------------------------------
void CPhysBox::InputDisableMotion( inputdata_t &inputdata )
{
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if ( pPhysicsObject != NULL )
{
pPhysicsObject->EnableMotion( false );
}
}
// Turn off floating simulation (and cost)
void CPhysBox::InputDisableFloating( inputdata_t &inputdata )
{
PhysEnableFloating( VPhysicsGetObject(), false );
}
//-----------------------------------------------------------------------------
// Purpose: If we're being held by the player's hand/physgun, force it to drop us
//-----------------------------------------------------------------------------
void CPhysBox::InputForceDrop( inputdata_t &inputdata )
{
if ( m_hCarryingPlayer )
{
m_hCarryingPlayer->ForceDropOfCarriedPhysObjects();
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysBox::Move( const Vector &direction )
{
VPhysicsGetObject()->ApplyForceCenter( direction );
}
// Update the visible representation of the physic system's representation of this object
void CPhysBox::VPhysicsUpdate( IPhysicsObject *pPhysics )
{
BaseClass::VPhysicsUpdate( pPhysics );
// if this is the first time we have moved, fire our target
if ( HasSpawnFlags( SF_PHYSBOX_ASLEEP ) )
{
if ( !pPhysics->IsAsleep() )
{
m_OnAwakened.FireOutput(this, this);
FireTargets( STRING(m_target), this, this, USE_TOGGLE, 0 );
RemoveSpawnFlags( SF_PHYSBOX_ASLEEP );
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysBox::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
{
if ( reason == PUNTED_BY_CANNON )
{
m_OnPhysGunPunt.FireOutput( pPhysGunUser, this );
}
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if ( pPhysicsObject && !pPhysicsObject->IsMoveable() )
{
if ( !HasSpawnFlags( SF_PHYSBOX_ENABLE_ON_PHYSCANNON ) )
return;
EnableMotion();
}
m_OnPhysGunPickup.FireOutput( pPhysGunUser, this );
// Are we just being punted?
if ( reason == PUNTED_BY_CANNON )
return;
if( reason == PICKED_UP_BY_CANNON )
{
m_OnPhysGunOnlyPickup.FireOutput( pPhysGunUser, this );
}
m_hCarryingPlayer = pPhysGunUser;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysBox::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
{
BaseClass::OnPhysGunDrop( pPhysGunUser, Reason );
m_hCarryingPlayer = NULL;
m_OnPhysGunDrop.FireOutput( pPhysGunUser, this );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysBox::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
BaseClass::VPhysicsCollision( index, pEvent );
IPhysicsObject *pPhysObj = pEvent->pObjects[!index];
// If we have a force to enable motion, and we're still disabled, check to see if this should enable us
if ( m_flForceToEnableMotion )
{
CBaseEntity *pOther = static_cast<CBaseEntity *>(pPhysObj->GetGameData());
// Don't allow the player to bump an object active if we've requested not to
if ( ( pOther && pOther->IsPlayer() && HasSpawnFlags( SF_PHYSBOX_PREVENT_PLAYER_TOUCH_ENABLE ) ) == false )
{
// Large enough to enable motion?
float flForce = pEvent->collisionSpeed * pEvent->pObjects[!index]->GetMass();
if ( flForce >= m_flForceToEnableMotion )
{
EnableMotion();
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CPhysBox::OnTakeDamage( const CTakeDamageInfo &info )
{
if ( IsMarkedForDeletion() )
return 0;
// note: if motion is disabled, OnTakeDamage can't apply physics force
int ret = BaseClass::OnTakeDamage( info );
if ( info.GetInflictor() )
{
m_OnDamaged.FireOutput( info.GetAttacker(), this );
}
// Have we been broken? If so, abort
if ( GetHealth() <= 0 )
return ret;
// If we have a force to enable motion, and we're still disabled, check to see if this should enable us
if ( m_flForceToEnableMotion )
{
// Large enough to enable motion?
float flForce = info.GetDamageForce().Length();
if ( flForce >= m_flForceToEnableMotion )
{
IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
if ( pPhysicsObject )
{
EnableMotion();
}
}
}
// Check our health against the threshold:
if( m_damageToEnableMotion > 0 && GetHealth() < m_damageToEnableMotion )
{
EnableMotion();
VPhysicsTakeDamage( info );
}
return ret;
}
//-----------------------------------------------------------------------------
// Purpose: Return true if this physbox has preferred carry angles
//-----------------------------------------------------------------------------
bool CPhysBox::HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer )
{
return HasSpawnFlags( SF_PHYSBOX_USEPREFERRED );
}
// ---------------------------------------------------------------------
//
// CPhysExplosion -- physically simulated explosion
//
// ---------------------------------------------------------------------
#define SF_PHYSEXPLOSION_NODAMAGE 0x0001
#define SF_PHYSEXPLOSION_PUSH_PLAYER 0x0002
#define SF_PHYSEXPLOSION_RADIAL 0x0004
#define SF_PHYSEXPLOSION_TEST_LOS 0x0008
#define SF_PHYSEXPLOSION_DISORIENT_PLAYER 0x0010
LINK_ENTITY_TO_CLASS( env_physexplosion, CPhysExplosion );
BEGIN_DATADESC( CPhysExplosion )
DEFINE_KEYFIELD( m_damage, FIELD_FLOAT, "magnitude" ),
DEFINE_KEYFIELD( m_radius, FIELD_FLOAT, "radius" ),
DEFINE_KEYFIELD( m_targetEntityName, FIELD_STRING, "targetentityname" ),
DEFINE_KEYFIELD( m_flInnerRadius, FIELD_FLOAT, "inner_radius" ),
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "Explode", InputExplode ),
// Outputs
DEFINE_OUTPUT( m_OnPushedPlayer, "OnPushedPlayer" ),
END_DATADESC()
void CPhysExplosion::Spawn( void )
{
SetMoveType( MOVETYPE_NONE );
SetSolid( SOLID_NONE );
SetModelName( NULL_STRING );
}
float CPhysExplosion::GetRadius( void )
{
float radius = m_radius;
if ( radius <= 0 )
{
// Use the same radius as combat
radius = m_damage * 2.5;
}
return radius;
}
CBaseEntity *CPhysExplosion::FindEntity( CBaseEntity *pEntity, CBaseEntity *pActivator, CBaseEntity *pCaller )
{
// Filter by name or classname
if ( m_targetEntityName != NULL_STRING )
{
// Try an explicit name first
CBaseEntity *pTarget = gEntList.FindEntityByName( pEntity, m_targetEntityName, NULL, pActivator, pCaller );
if ( pTarget != NULL )
return pTarget;
// Failing that, try a classname
return gEntList.FindEntityByClassnameWithin( pEntity, STRING(m_targetEntityName), GetAbsOrigin(), GetRadius() );
}
// Just find anything in the radius
return gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), GetRadius() );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysExplosion::InputExplode( inputdata_t &inputdata )
{
Explode( inputdata.pActivator, inputdata.pCaller );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysExplosion::Explode( CBaseEntity *pActivator, CBaseEntity *pCaller )
{
CBaseEntity *pEntity = NULL;
float adjustedDamage, falloff, flDist;
Vector vecSpot, vecOrigin;
falloff = 1.0 / 2.5;
// iterate on all entities in the vicinity.
// I've removed the traceline heuristic from phys explosions. SO right now they will
// affect entities through walls. (sjb)
// UNDONE: Try tracing world-only?
while ((pEntity = FindEntity( pEntity, pActivator, pCaller )) != NULL)
{
// UNDONE: Ask the object if it should get force if it's not MOVETYPE_VPHYSICS?
if ( pEntity->m_takedamage != DAMAGE_NO && (pEntity->GetMoveType() == MOVETYPE_VPHYSICS || (pEntity->VPhysicsGetObject() /*&& !pEntity->IsPlayer()*/)) )
{
vecOrigin = GetAbsOrigin();
vecSpot = pEntity->BodyTarget( vecOrigin );
// Squash this down to a circle
if ( HasSpawnFlags( SF_PHYSEXPLOSION_RADIAL ) )
{
vecOrigin[2] = vecSpot[2];
}
// decrease damage for an ent that's farther from the bomb.
flDist = ( vecOrigin - vecSpot ).Length();
if( m_radius == 0 || flDist <= m_radius )
{
if ( HasSpawnFlags( SF_PHYSEXPLOSION_TEST_LOS ) )
{
Vector vecStartPos = GetAbsOrigin();
Vector vecEndPos = pEntity->BodyTarget( vecStartPos, false );
if ( m_flInnerRadius != 0.0f )
{
// Find a point on our inner radius sphere to begin from
Vector vecDirToTarget = ( vecEndPos - vecStartPos );
VectorNormalize( vecDirToTarget );
vecStartPos = GetAbsOrigin() + ( vecDirToTarget * m_flInnerRadius );
}
trace_t tr;
UTIL_TraceLine( vecStartPos,
pEntity->BodyTarget( vecStartPos, false ),
MASK_SOLID_BRUSHONLY,
this,
COLLISION_GROUP_NONE,
&tr );
// Shielded
if ( tr.fraction < 1.0f && tr.m_pEnt != pEntity )
continue;
}
adjustedDamage = flDist * falloff;
adjustedDamage = m_damage - adjustedDamage;
if ( adjustedDamage < 1 )
{
adjustedDamage = 1;
}
CTakeDamageInfo info( this, this, adjustedDamage, DMG_BLAST );
CalculateExplosiveDamageForce( &info, (vecSpot - vecOrigin), vecOrigin );
if ( HasSpawnFlags( SF_PHYSEXPLOSION_PUSH_PLAYER ) )
{
if ( pEntity->IsPlayer() )
{
Vector vecPushDir = ( pEntity->BodyTarget( GetAbsOrigin(), false ) - GetAbsOrigin() );
float dist = VectorNormalize( vecPushDir );
float flFalloff = RemapValClamped( dist, m_radius, m_radius*0.75f, 0.0f, 1.0f );
if ( HasSpawnFlags( SF_PHYSEXPLOSION_DISORIENT_PLAYER ) )
{
//Disorient the player
QAngle vecDeltaAngles;
vecDeltaAngles.x = random->RandomInt( -30, 30 );
vecDeltaAngles.y = random->RandomInt( -30, 30 );
vecDeltaAngles.z = 0.0f;
CBasePlayer *pPlayer = ToBasePlayer( pEntity );
pPlayer->SnapEyeAngles( GetLocalAngles() + vecDeltaAngles );
pEntity->ViewPunch( vecDeltaAngles );
}
Vector vecPush = (vecPushDir*m_damage*flFalloff*2.0f);
if ( pEntity->GetFlags() & FL_BASEVELOCITY )
{
vecPush = vecPush + pEntity->GetBaseVelocity();
}
if ( vecPush.z > 0 && (pEntity->GetFlags() & FL_ONGROUND) )
{
pEntity->SetGroundEntity( NULL );
Vector origin = pEntity->GetAbsOrigin();
origin.z += 1.0f;
pEntity->SetAbsOrigin( origin );
}
pEntity->SetBaseVelocity( vecPush );
pEntity->AddFlag( FL_BASEVELOCITY );
// Fire an output that the player has been pushed
m_OnPushedPlayer.FireOutput( this, this );
continue;
}
}
if ( HasSpawnFlags( SF_PHYSEXPLOSION_NODAMAGE ) )
{
pEntity->VPhysicsTakeDamage( info );
}
else
{
pEntity->TakeDamage( info );
}
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Draw any debug text overlays
// Output : Current text offset from the top
//-----------------------------------------------------------------------------
int CPhysExplosion::DrawDebugTextOverlays( void )
{
int text_offset = BaseClass::DrawDebugTextOverlays();
if (m_debugOverlays & OVERLAY_TEXT_BIT)
{
char tempstr[512];
// print magnitude
Q_snprintf(tempstr,sizeof(tempstr)," magnitude: %f", m_damage);
EntityText(text_offset,tempstr,0);
text_offset++;
// print target entity
Q_snprintf(tempstr,sizeof(tempstr)," limit to: %s", STRING(m_targetEntityName));
EntityText(text_offset,tempstr,0);
text_offset++;
}
return text_offset;
}
//==================================================
// CPhysImpact
//==================================================
#define bitsPHYSIMPACT_NOFALLOFF 0x00000001
#define bitsPHYSIMPACT_INFINITE_LENGTH 0x00000002
#define bitsPHYSIMPACT_IGNORE_MASS 0x00000004
#define bitsPHYSIMPACT_IGNORE_NORMAL 0x00000008
#define DEFAULT_EXPLODE_DISTANCE 256
LINK_ENTITY_TO_CLASS( env_physimpact, CPhysImpact );
BEGIN_DATADESC( CPhysImpact )
DEFINE_KEYFIELD( m_damage, FIELD_FLOAT, "magnitude" ),
DEFINE_KEYFIELD( m_distance, FIELD_FLOAT, "distance" ),
DEFINE_KEYFIELD( m_directionEntityName,FIELD_STRING, "directionentityname" ),
// Function pointers
DEFINE_FUNCTION( PointAtEntity ),
DEFINE_INPUTFUNC( FIELD_VOID, "Impact", InputImpact ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysImpact::Activate( void )
{
BaseClass::Activate();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysImpact::Spawn( void )
{
SetMoveType( MOVETYPE_NONE );
SetSolid( SOLID_NONE );
SetModelName( NULL_STRING );
//If not targetted, and no distance is set, give it a default value
if ( m_distance == 0 )
{
m_distance = DEFAULT_EXPLODE_DISTANCE;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysImpact::PointAtEntity( void )
{
//If we're not targetted at anything, don't bother
if ( m_directionEntityName == NULL_STRING )
return;
UTIL_PointAtNamedEntity( this, m_directionEntityName );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pActivator -
// *pCaller -
// useType -
// value -
//-----------------------------------------------------------------------------
void CPhysImpact::InputImpact( inputdata_t &inputdata )
{
Vector dir;
trace_t trace;
//If we have a direction target, setup to point at it
if ( m_directionEntityName != NULL_STRING )
{
PointAtEntity();
}
AngleVectors( GetAbsAngles(), &dir );
//Setup our trace information
float dist = HasSpawnFlags( bitsPHYSIMPACT_INFINITE_LENGTH ) ? MAX_TRACE_LENGTH : m_distance;
Vector start = GetAbsOrigin();
Vector end = start + ( dir * dist );
//Trace out
UTIL_TraceLine( start, end, MASK_SHOT, this, COLLISION_GROUP_NONE, &trace );
if ( trace.startsolid )
{
// ep1_citadel_04 has a phys_impact just behind another entity, so if we startsolid then
// bump out just a little and retry the trace
Vector startOffset = start + ( dir * 0.1 );
UTIL_TraceLine( startOffset , end, MASK_SHOT, this, COLLISION_GROUP_NONE, &trace );
}
if( debug_physimpact.GetBool() )
{
NDebugOverlay::Cross3D( start, 24, 255, 255, 255, false, 30 );
NDebugOverlay::Line( trace.startpos, trace.endpos, 0, 255, 0, false, 30 );
}
if ( trace.fraction != 1.0 )
{
// if inside the object, just go opposite the direction
if ( trace.startsolid )
{
trace.plane.normal = -dir;
}
CBaseEntity *pEnt = trace.m_pEnt;
IPhysicsObject *pPhysics = pEnt->VPhysicsGetObject();
//If the entity is valid, hit it
if ( ( pEnt != NULL ) && ( pPhysics != NULL ) )
{
CTakeDamageInfo info;
info.SetAttacker( this);
info.SetInflictor( this );
info.SetDamage( 0 );
info.SetDamageForce( vec3_origin );
info.SetDamageType( DMG_GENERIC );
pEnt->DispatchTraceAttack( info, dir, &trace );
ApplyMultiDamage();
//Damage falls off unless specified or the ray's length is infinite
float damage = HasSpawnFlags( bitsPHYSIMPACT_NOFALLOFF | bitsPHYSIMPACT_INFINITE_LENGTH ) ?
m_damage : (m_damage * (1.0f-trace.fraction));
if ( HasSpawnFlags( bitsPHYSIMPACT_IGNORE_MASS ) )
{
damage *= pPhysics->GetMass();
}
if( debug_physimpact.GetBool() )
{
NDebugOverlay::Line( trace.endpos, trace.endpos + trace.plane.normal * -128, 255, 0, 0, false, 30 );
}
// Legacy entities applied the force along the impact normal, which yielded unpredictable results.
if ( !HasSpawnFlags( bitsPHYSIMPACT_IGNORE_NORMAL ) )
{
dir = -trace.plane.normal;
}
pPhysics->ApplyForceOffset( damage * dir * phys_pushscale.GetFloat(), trace.endpos );
}
}
}
class CSimplePhysicsBrush : public CBaseEntity
{
DECLARE_CLASS( CSimplePhysicsBrush, CBaseEntity );
public:
void Spawn()
{
SetModel( STRING( GetModelName() ) );
SetMoveType( MOVETYPE_VPHYSICS );
SetSolid( SOLID_VPHYSICS );
m_takedamage = DAMAGE_EVENTS_ONLY;
}
};
LINK_ENTITY_TO_CLASS( simple_physics_brush, CSimplePhysicsBrush );
class CSimplePhysicsProp : public CBaseProp
{
DECLARE_CLASS( CSimplePhysicsProp, CBaseProp );
public:
void Spawn()
{
BaseClass::Spawn();
SetMoveType( MOVETYPE_VPHYSICS );
SetSolid( SOLID_VPHYSICS );
m_takedamage = DAMAGE_EVENTS_ONLY;
}
int ObjectCaps()
{
int caps = BaseClass::ObjectCaps() | FCAP_WCEDIT_POSITION;
if ( CBasePlayer::CanPickupObject( this, 35, 128 ) )
{
caps |= FCAP_IMPULSE_USE;
}
return caps;
}
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
CBasePlayer *pPlayer = ToBasePlayer( pActivator );
if ( pPlayer )
{
pPlayer->PickupObject( this );
}
}
};
LINK_ENTITY_TO_CLASS( simple_physics_prop, CSimplePhysicsProp );
// UNDONE: Is this worth it?, just recreate the object instead? (that happens when this returns false anyway)
// recreating works, but is more expensive and won't inherit properties (velocity, constraints, etc)
bool TransferPhysicsObject( CBaseEntity *pFrom, CBaseEntity *pTo, bool wakeUp )
{
IPhysicsObject *pVPhysics = pFrom->VPhysicsGetObject();
if ( !pVPhysics || pVPhysics->IsStatic() )
return false;
// clear out the pointer so it won't get deleted
pFrom->VPhysicsSwapObject( NULL );
// remove any AI behavior bound to it
pVPhysics->RemoveShadowController();
// transfer to the new owner
pTo->VPhysicsSetObject( pVPhysics );
pVPhysics->SetGameData( (void *)pTo );
pTo->VPhysicsUpdate( pVPhysics );
// may have been temporarily disabled by the old object
pVPhysics->EnableMotion( true );
pVPhysics->EnableGravity( true );
// Update for the new entity solid type
pVPhysics->RecheckCollisionFilter();
if ( wakeUp )
{
pVPhysics->Wake();
}
return true;
}
// UNDONE: Move/rename this function
static CBaseEntity *CreateSimplePhysicsObject( CBaseEntity *pEntity, bool createAsleep, bool createAsDebris )
{
CBaseEntity *pPhysEntity = NULL;
int modelindex = pEntity->GetModelIndex();
const model_t *model = modelinfo->GetModel( modelindex );
if ( model && modelinfo->GetModelType(model) == mod_brush )
{
pPhysEntity = CreateEntityByName( "simple_physics_brush" );
}
else
{
pPhysEntity = CreateEntityByName( "simple_physics_prop" );
}
pPhysEntity->KeyValue( "model", STRING(pEntity->GetModelName()) );
pPhysEntity->SetAbsOrigin( pEntity->GetAbsOrigin() );
pPhysEntity->SetAbsAngles( pEntity->GetAbsAngles() );
pPhysEntity->Spawn();
if ( !TransferPhysicsObject( pEntity, pPhysEntity, !createAsleep ) )
{
pPhysEntity->VPhysicsInitNormal( SOLID_VPHYSICS, 0, createAsleep );
if ( createAsDebris )
pPhysEntity->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
}
return pPhysEntity;
}
#define SF_CONVERT_ASLEEP 0x0001
#define SF_CONVERT_AS_DEBRIS 0x0002
class CPhysConvert : public CLogicalEntity
{
DECLARE_CLASS( CPhysConvert, CLogicalEntity );
public:
CPhysConvert( void ) : m_flMassOverride( 0.0f ) {};
COutputEvent m_OnConvert;
// Input handlers
void InputConvertTarget( inputdata_t &inputdata );
DECLARE_DATADESC();
private:
string_t m_swapModel;
float m_flMassOverride;
};
LINK_ENTITY_TO_CLASS( phys_convert, CPhysConvert );
BEGIN_DATADESC( CPhysConvert )
DEFINE_KEYFIELD( m_swapModel, FIELD_STRING, "swapmodel" ),
DEFINE_KEYFIELD( m_flMassOverride, FIELD_FLOAT, "massoverride" ),
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "ConvertTarget", InputConvertTarget ),
// Outputs
DEFINE_OUTPUT( m_OnConvert, "OnConvert"),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: Input handler that converts our target to a physics object.
//-----------------------------------------------------------------------------
void CPhysConvert::InputConvertTarget( inputdata_t &inputdata )
{
bool createAsleep = HasSpawnFlags(SF_CONVERT_ASLEEP);
bool createAsDebris = HasSpawnFlags(SF_CONVERT_AS_DEBRIS);
// Fire output
m_OnConvert.FireOutput( inputdata.pActivator, this );
CBaseEntity *entlist[512];
CBaseEntity *pSwap = gEntList.FindEntityByName( NULL, m_swapModel, NULL, inputdata.pActivator, inputdata.pCaller );
CBaseEntity *pEntity = NULL;
int count = 0;
while ( (pEntity = gEntList.FindEntityByName( pEntity, m_target, NULL, inputdata.pActivator, inputdata.pCaller )) != NULL )
{
entlist[count++] = pEntity;
if ( count >= ARRAYSIZE(entlist) )
break;
}
// if we're swapping to model out, don't loop over more than one object
// multiple objects with the same brush model will render, but the dynamic lights
// and decals will be shared between the two instances...
if ( pSwap && count > 0 )
{
count = 1;
}
for ( int i = 0; i < count; i++ )
{
pEntity = entlist[i];
// don't convert something that is already physics based
if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
{
Msg( "ERROR phys_convert %s ! Already MOVETYPE_VPHYSICS\n", STRING(pEntity->m_iClassname) );
continue;
}
UnlinkFromParent( pEntity );
if ( pSwap )
{
// we can't reuse this physics object, so kill it
pEntity->VPhysicsDestroyObject();
pEntity->SetModel( STRING(pSwap->GetModelName()) );
}
// created phys object, now move hierarchy over
CBaseEntity *pPhys = CreateSimplePhysicsObject( pEntity, createAsleep, createAsDebris );
if ( pPhys )
{
// Override the mass if specified
if ( m_flMassOverride > 0 )
{
IPhysicsObject *pPhysObj = pPhys->VPhysicsGetObject();
if ( pPhysObj )
{
pPhysObj->SetMass( m_flMassOverride );
}
}
pPhys->SetName( pEntity->GetEntityName() );
UTIL_TransferPoseParameters( pEntity, pPhys );
TransferChildren( pEntity, pPhys );
pEntity->AddSolidFlags( FSOLID_NOT_SOLID );
pEntity->AddEffects( EF_NODRAW );
UTIL_Remove( pEntity );
}
}
}
//============================================================================================================
// PHYS MAGNET
//============================================================================================================
#define SF_MAGNET_ASLEEP 0x0001
#define SF_MAGNET_MOTIONDISABLED 0x0002
#define SF_MAGNET_SUCK 0x0004
#define SF_MAGNET_ALLOWROTATION 0x0008
#define SF_MAGNET_COAST_HACK 0x0010
LINK_ENTITY_TO_CLASS( phys_magnet, CPhysMagnet );
// BUGBUG: This won't work! Right now you can't save physics pointers inside an embedded type!
BEGIN_SIMPLE_DATADESC( magnetted_objects_t )
DEFINE_PHYSPTR( pConstraint ),
DEFINE_FIELD( hEntity, FIELD_EHANDLE ),
END_DATADESC()
BEGIN_DATADESC( CPhysMagnet )
// Outputs
DEFINE_OUTPUT( m_OnMagnetAttach, "OnAttach" ),
DEFINE_OUTPUT( m_OnMagnetDetach, "OnDetach" ),
// Keys
DEFINE_KEYFIELD( m_massScale, FIELD_FLOAT, "massScale" ),
DEFINE_KEYFIELD( m_iszOverrideScript, FIELD_STRING, "overridescript" ),
DEFINE_KEYFIELD( m_iMaxObjectsAttached, FIELD_INTEGER, "maxobjects" ),
DEFINE_KEYFIELD( m_forceLimit, FIELD_FLOAT, "forcelimit" ),
DEFINE_KEYFIELD( m_torqueLimit, FIELD_FLOAT, "torquelimit" ),
DEFINE_UTLVECTOR( m_MagnettedEntities, FIELD_EMBEDDED ),
DEFINE_PHYSPTR( m_pConstraintGroup ),
DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
DEFINE_FIELD( m_bHasHitSomething, FIELD_BOOLEAN ),
DEFINE_FIELD( m_flTotalMass, FIELD_FLOAT ),
DEFINE_FIELD( m_flRadius, FIELD_FLOAT ),
DEFINE_FIELD( m_flNextSuckTime, FIELD_FLOAT ),
// Inputs
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose: SendProxy that converts the magnet's attached object UtlVector to entindexes
//-----------------------------------------------------------------------------
void SendProxy_MagnetAttachedObjectList( const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
{
CPhysMagnet *pMagnet = (CPhysMagnet*)pData;
// If this assertion fails, then SendProxyArrayLength_MagnetAttachedArray must have failed.
Assert( iElement < pMagnet->GetNumAttachedObjects() );
pOut->m_Int = pMagnet->GetAttachedObject(iElement)->entindex();
}
int SendProxyArrayLength_MagnetAttachedArray( const void *pStruct, int objectID )
{
CPhysMagnet *pMagnet = (CPhysMagnet*)pStruct;
return pMagnet->GetNumAttachedObjects();
}
IMPLEMENT_SERVERCLASS_ST( CPhysMagnet, DT_PhysMagnet )
// ROBIN: Disabled because we don't need it anymore
/*
SendPropArray2(
SendProxyArrayLength_MagnetAttachedArray,
SendPropInt("magnetattached_array_element", 0, 4, 10, SPROP_UNSIGNED, SendProxy_MagnetAttachedObjectList),
128,
0,
"magnetattached_array"
)
*/
END_SEND_TABLE()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CPhysMagnet::CPhysMagnet( void )
{
m_forceLimit = 0;
m_torqueLimit = 0;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CPhysMagnet::~CPhysMagnet( void )
{
DetachAll();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysMagnet::Spawn( void )
{
Precache();
SetMoveType( MOVETYPE_NONE );
SetSolid( SOLID_VPHYSICS );
SetModel( STRING( GetModelName() ) );
m_takedamage = DAMAGE_EVENTS_ONLY;
solid_t tmpSolid;
PhysModelParseSolid( tmpSolid, this, GetModelIndex() );
if ( m_massScale > 0 )
{
tmpSolid.params.mass *= m_massScale;
}
PhysSolidOverride( tmpSolid, m_iszOverrideScript );
VPhysicsInitNormal( GetSolid(), GetSolidFlags(), true, &tmpSolid );
// Wake it up if not asleep
if ( !HasSpawnFlags(SF_MAGNET_ASLEEP) )
{
VPhysicsGetObject()->Wake();
}
if ( HasSpawnFlags(SF_MAGNET_MOTIONDISABLED) )
{
VPhysicsGetObject()->EnableMotion( false );
}
m_bActive = true;
m_pConstraintGroup = NULL;
m_flTotalMass = 0;
m_flNextSuckTime = 0;
BaseClass::Spawn();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysMagnet::Precache( void )
{
PrecacheModel( STRING( GetModelName() ) );
BaseClass::Precache();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysMagnet::Touch( CBaseEntity *pOther )
{
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysMagnet::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
{
int otherIndex = !index;
CBaseEntity *pOther = pEvent->pEntities[otherIndex];
// Ignore triggers
if ( pOther->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
return;
m_bHasHitSomething = true;
DoMagnetSuck( pEvent->pEntities[!index] );
// Don't pickup if we're not active
if ( !m_bActive )
return;
// Hit our maximum?
if ( m_iMaxObjectsAttached && m_iMaxObjectsAttached <= GetNumAttachedObjects() )
return;
// This is a hack to solve players (Erik) stacking stuff on their jeeps in coast_01
// and being screwed when the crane can't pick them up. We need to get rid of the object.
if ( HasSpawnFlags( SF_MAGNET_COAST_HACK ) )
{
// If the other isn't the jeep, we need to get rid of it
if ( !FClassnameIs( pOther, "prop_vehicle_jeep" ) )
{
// If it takes damage, destroy it
if ( pOther->m_takedamage != DAMAGE_NO && pOther->m_takedamage != DAMAGE_EVENTS_ONLY )
{
CTakeDamageInfo info( this, this, pOther->GetHealth(), DMG_GENERIC | DMG_PREVENT_PHYSICS_FORCE );
pOther->TakeDamage( info );
}
else if ( pEvent->pObjects[ otherIndex ]->IsMoveable() )
{
// Otherwise, we're screwed, so just remove it
UTIL_Remove( pOther );
}
else
{
Warning( "CPhysMagnet %s:%d blocking magnet\n",
pOther->GetClassname(), pOther->entindex() );
}
return;
}
}
// Make sure it's made of metal
const surfacedata_t *phit = physprops->GetSurfaceData( pEvent->surfaceProps[otherIndex] );
char cTexType = phit->game.material;
if ( cTexType != CHAR_TEX_METAL && cTexType != CHAR_TEX_COMPUTER )
{
// If we don't have a model, we're done. The texture we hit wasn't metal.
if ( !pOther->GetBaseAnimating() )
return;
// If we have a model that wants to be metal, even though we hit a non-metal texture, we'll stick to it
if ( Q_strncmp( Studio_GetDefaultSurfaceProps( pOther->GetBaseAnimating()->GetModelPtr() ), "metal", 5 ) )
return;
}
IPhysicsObject *pPhysics = pOther->VPhysicsGetObject();
if ( pPhysics && pOther->GetMoveType() == MOVETYPE_VPHYSICS && pPhysics->IsMoveable() )
{
// Make sure we haven't already got this sucker on the magnet
int iCount = m_MagnettedEntities.Count();
for ( int i = 0; i < iCount; i++ )
{
if ( m_MagnettedEntities[i].hEntity == pOther )
return;
}
// We want to cast a long way to ensure our shadow shows up
pOther->SetShadowCastDistance( 2048 );
// Create a constraint between the magnet and this sucker
IPhysicsObject *pMagnetPhysObject = VPhysicsGetObject();
Assert( pMagnetPhysObject );
magnetted_objects_t newEntityOnMagnet;
newEntityOnMagnet.hEntity = pOther;
// Use the right constraint
if ( HasSpawnFlags( SF_MAGNET_ALLOWROTATION ) )
{
constraint_ballsocketparams_t ballsocket;
ballsocket.Defaults();
ballsocket.constraint.Defaults();
ballsocket.constraint.forceLimit = lbs2kg(m_forceLimit);
ballsocket.constraint.torqueLimit = lbs2kg(m_torqueLimit);
Vector vecCollisionPoint;
pEvent->pInternalData->GetContactPoint( vecCollisionPoint );
pMagnetPhysObject->WorldToLocal( &ballsocket.constraintPosition[0], vecCollisionPoint );
pPhysics->WorldToLocal( &ballsocket.constraintPosition[1], vecCollisionPoint );
//newEntityOnMagnet.pConstraint = physenv->CreateBallsocketConstraint( pMagnetPhysObject, pPhysics, m_pConstraintGroup, ballsocket );
newEntityOnMagnet.pConstraint = physenv->CreateBallsocketConstraint( pMagnetPhysObject, pPhysics, NULL, ballsocket );
}
else
{
constraint_fixedparams_t fixed;
fixed.Defaults();
fixed.InitWithCurrentObjectState( pMagnetPhysObject, pPhysics );
fixed.constraint.Defaults();
fixed.constraint.forceLimit = lbs2kg(m_forceLimit);
fixed.constraint.torqueLimit = lbs2kg(m_torqueLimit);
// FIXME: Use the magnet's constraint group.
//newEntityOnMagnet.pConstraint = physenv->CreateFixedConstraint( pMagnetPhysObject, pPhysics, m_pConstraintGroup, fixed );
newEntityOnMagnet.pConstraint = physenv->CreateFixedConstraint( pMagnetPhysObject, pPhysics, NULL, fixed );
}
newEntityOnMagnet.pConstraint->SetGameData( (void *) this );
m_MagnettedEntities.AddToTail( newEntityOnMagnet );
m_flTotalMass += pPhysics->GetMass();
}
DoMagnetSuck( pOther );
m_OnMagnetAttach.FireOutput( this, this );
BaseClass::VPhysicsCollision( index, pEvent );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysMagnet::DoMagnetSuck( CBaseEntity *pOther )
{
if ( !HasSpawnFlags( SF_MAGNET_SUCK ) )
return;
if ( !m_bActive )
return;
// Don't repeatedly suck
if ( m_flNextSuckTime > gpGlobals->curtime )
return;
// Look for physics objects underneath the magnet and suck them onto it
Vector vecCheckPos, vecSuckPoint;
VectorTransform( Vector(0,0,-96), EntityToWorldTransform(), vecCheckPos );
VectorTransform( Vector(0,0,-64), EntityToWorldTransform(), vecSuckPoint );
CBaseEntity *pEntities[20];
int iNumEntities = UTIL_EntitiesInSphere( pEntities, 20, vecCheckPos, 80.0, 0 );
for ( int i = 0; i < iNumEntities; i++ )
{
CBaseEntity *pEntity = pEntities[i];
if ( !pEntity || pEntity == pOther )
continue;
IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
if ( pPhys && pEntity->GetMoveType() == MOVETYPE_VPHYSICS && pPhys->GetMass() < 5000 )
{
// Do we have line of sight to it?
trace_t tr;
UTIL_TraceLine( GetAbsOrigin(), pEntity->GetAbsOrigin(), MASK_SHOT, this, 0, &tr );
if ( tr.fraction == 1.0 || tr.m_pEnt == pEntity )
{
// Pull it towards the magnet
Vector vecVelocity = (vecSuckPoint - pEntity->GetAbsOrigin());
VectorNormalize(vecVelocity);
vecVelocity *= 5 * pPhys->GetMass();
pPhys->AddVelocity( &vecVelocity, NULL );
}
}
}
m_flNextSuckTime = gpGlobals->curtime + 2.0;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysMagnet::SetConstraintGroup( IPhysicsConstraintGroup *pGroup )
{
m_pConstraintGroup = pGroup;
}
//-----------------------------------------------------------------------------
// Purpose: Make the magnet active
//-----------------------------------------------------------------------------
void CPhysMagnet::InputTurnOn( inputdata_t &inputdata )
{
m_bActive = true;
}
//-----------------------------------------------------------------------------
// Purpose: Make the magnet inactive. Drop everything it's got hooked on.
//-----------------------------------------------------------------------------
void CPhysMagnet::InputTurnOff( inputdata_t &inputdata )
{
m_bActive = false;
DetachAll();
}
//-----------------------------------------------------------------------------
// Purpose: Toggle the magnet's active state
//-----------------------------------------------------------------------------
void CPhysMagnet::InputToggle( inputdata_t &inputdata )
{
if ( m_bActive )
{
InputTurnOff( inputdata );
}
else
{
InputTurnOn( inputdata );
}
}
//-----------------------------------------------------------------------------
// Purpose: One of our magnet constraints broke
//-----------------------------------------------------------------------------
void CPhysMagnet::ConstraintBroken( IPhysicsConstraint *pConstraint )
{
// Find the entity that was constrained and release it
int iCount = m_MagnettedEntities.Count();
for ( int i = 0; i < iCount; i++ )
{
if ( m_MagnettedEntities[i].hEntity.Get() != NULL && m_MagnettedEntities[i].pConstraint == pConstraint )
{
IPhysicsObject *pPhysObject = m_MagnettedEntities[i].hEntity->VPhysicsGetObject();
if( pPhysObject != NULL )
{
m_flTotalMass -= pPhysObject->GetMass();
}
m_MagnettedEntities.Remove(i);
break;
}
}
m_OnMagnetDetach.FireOutput( this, this );
physenv->DestroyConstraint( pConstraint );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPhysMagnet::DetachAll( void )
{
// Make sure we haven't already got this sucker on the magnet
int iCount = m_MagnettedEntities.Count();
for ( int i = 0; i < iCount; i++ )
{
// Delay a couple seconds to reset to the default shadow cast behavior
if ( m_MagnettedEntities[i].hEntity )
{
m_MagnettedEntities[i].hEntity->SetShadowCastDistance( 0, 2.0f );
}
physenv->DestroyConstraint( m_MagnettedEntities[i].pConstraint );
}
m_MagnettedEntities.Purge();
m_flTotalMass = 0;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
int CPhysMagnet::GetNumAttachedObjects( void )
{
return m_MagnettedEntities.Count();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
float CPhysMagnet::GetTotalMassAttachedObjects( void )
{
return m_flTotalMass;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CBaseEntity *CPhysMagnet::GetAttachedObject( int iIndex )
{
Assert( iIndex < GetNumAttachedObjects() );
return m_MagnettedEntities[iIndex].hEntity;
}
class CInfoMassCenter : public CPointEntity
{
DECLARE_CLASS( CInfoMassCenter, CPointEntity );
public:
void Spawn( void )
{
if ( m_target != NULL_STRING )
{
masscenteroverride_t params;
params.SnapToPoint( m_target, GetAbsOrigin() );
PhysSetMassCenterOverride( params );
UTIL_Remove( this );
}
}
};
LINK_ENTITY_TO_CLASS( info_mass_center, CInfoMassCenter );
// =============================================================
// point_push
// =============================================================
class CPointPush : public CPointEntity
{
public:
DECLARE_CLASS( CPointPush, CPointEntity );
virtual void Activate( void );
void PushThink( void );
void InputEnable( inputdata_t &inputdata );
void InputDisable( inputdata_t &inputdata );
DECLARE_DATADESC();
private:
inline void PushEntity( CBaseEntity *pTarget );
bool m_bEnabled;
float m_flMagnitude;
float m_flRadius;
float m_flInnerRadius; // Inner radius where the push eminates from (on a sphere)
};
LINK_ENTITY_TO_CLASS( point_push, CPointPush );
BEGIN_DATADESC( CPointPush )
DEFINE_THINKFUNC( PushThink ),
DEFINE_KEYFIELD( m_bEnabled, FIELD_BOOLEAN, "enabled" ),
DEFINE_KEYFIELD( m_flMagnitude, FIELD_FLOAT, "magnitude" ),
DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
DEFINE_KEYFIELD( m_flInnerRadius,FIELD_FLOAT, "inner_radius" ),
DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
END_DATADESC();
// Spawnflags
#define SF_PUSH_TEST_LOS 0x0001
#define SF_PUSH_DIRECTIONAL 0x0002
#define SF_PUSH_NO_FALLOFF 0x0004
#define SF_PUSH_PLAYER 0x0008
#define SF_PUSH_PHYSICS 0x0010
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPointPush::Activate( void )
{
if ( m_bEnabled )
{
SetThink( &CPointPush::PushThink );
SetNextThink( gpGlobals->curtime + 0.05f );
}
BaseClass::Activate();
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pTarget -
//-----------------------------------------------------------------------------
void CPointPush::PushEntity( CBaseEntity *pTarget )
{
Vector vecPushDir;
if ( HasSpawnFlags( SF_PUSH_DIRECTIONAL ) )
{
GetVectors( &vecPushDir, NULL, NULL );
}
else
{
vecPushDir = ( pTarget->BodyTarget( GetAbsOrigin(), false ) - GetAbsOrigin() );
}
float dist = VectorNormalize( vecPushDir );
float flFalloff = ( HasSpawnFlags( SF_PUSH_NO_FALLOFF ) ) ? 1.0f : RemapValClamped( dist, m_flRadius, m_flRadius*0.25f, 0.0f, 1.0f );
switch( pTarget->GetMoveType() )
{
case MOVETYPE_NONE:
case MOVETYPE_PUSH:
case MOVETYPE_NOCLIP:
break;
case MOVETYPE_VPHYSICS:
{
IPhysicsObject *pPhys = pTarget->VPhysicsGetObject();
if ( pPhys )
{
// UNDONE: Assume the velocity is for a 100kg object, scale with mass
pPhys->ApplyForceCenter( m_flMagnitude * flFalloff * 100.0f * vecPushDir * pPhys->GetMass() * gpGlobals->frametime );
return;
}
}
break;
case MOVETYPE_STEP:
{
// NPCs cannot be lifted up properly, they need to move in 2D
vecPushDir.z = 0.0f;
// NOTE: Falls through!
}
default:
{
Vector vecPush = (m_flMagnitude * vecPushDir * flFalloff);
if ( pTarget->GetFlags() & FL_BASEVELOCITY )
{
vecPush = vecPush + pTarget->GetBaseVelocity();
}
if ( vecPush.z > 0 && (pTarget->GetFlags() & FL_ONGROUND) )
{
pTarget->SetGroundEntity( NULL );
Vector origin = pTarget->GetAbsOrigin();
origin.z += 1.0f;
pTarget->SetAbsOrigin( origin );
}
pTarget->SetBaseVelocity( vecPush );
pTarget->AddFlag( FL_BASEVELOCITY );
}
break;
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPointPush::PushThink( void )
{
// Get a collection of entities in a radius around us
CBaseEntity *pEnts[256];
int numEnts = UTIL_EntitiesInSphere( pEnts, 256, GetAbsOrigin(), m_flRadius, 0 );
for ( int i = 0; i < numEnts; i++ )
{
// Must be solid
if ( pEnts[i]->IsSolid() == false )
continue;
// Cannot be parented (only push parents)
if ( pEnts[i]->GetMoveParent() != NULL )
continue;
// Must be moveable
if ( pEnts[i]->GetMoveType() != MOVETYPE_VPHYSICS &&
pEnts[i]->GetMoveType() != MOVETYPE_WALK &&
pEnts[i]->GetMoveType() != MOVETYPE_STEP )
continue;
// If we don't want to push players, don't
if ( pEnts[i]->IsPlayer() && HasSpawnFlags( SF_PUSH_PLAYER ) == false )
continue;
// If we don't want to push physics, don't
if ( pEnts[i]->GetMoveType() == MOVETYPE_VPHYSICS && HasSpawnFlags( SF_PUSH_PHYSICS ) == false )
continue;
// Test for LOS if asked to
if ( HasSpawnFlags( SF_PUSH_TEST_LOS ) )
{
Vector vecStartPos = GetAbsOrigin();
Vector vecEndPos = pEnts[i]->BodyTarget( vecStartPos, false );
if ( m_flInnerRadius != 0.0f )
{
// Find a point on our inner radius sphere to begin from
Vector vecDirToTarget = ( vecEndPos - vecStartPos );
VectorNormalize( vecDirToTarget );
vecStartPos = GetAbsOrigin() + ( vecDirToTarget * m_flInnerRadius );
}
trace_t tr;
UTIL_TraceLine( vecStartPos,
pEnts[i]->BodyTarget( vecStartPos, false ),
MASK_SOLID_BRUSHONLY,
this,
COLLISION_GROUP_NONE,
&tr );
// Shielded
if ( tr.fraction < 1.0f && tr.m_pEnt != pEnts[i] )
continue;
}
// Push it along
PushEntity( pEnts[i] );
}
// Set us up for the next think
SetNextThink( gpGlobals->curtime + 0.05f );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPointPush::InputEnable( inputdata_t &inputdata )
{
m_bEnabled = true;
SetThink( &CPointPush::PushThink );
SetNextThink( gpGlobals->curtime + 0.05f );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CPointPush::InputDisable( inputdata_t &inputdata )
{
m_bEnabled = false;
SetThink( NULL );
SetNextThink( gpGlobals->curtime );
}