From f56bb35301836e56582a575a75864392a0177875 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B8rgen=20P=2E=20Tjern=C3=B8?= Date: Mon, 2 Dec 2013 19:31:46 -0800 Subject: Fix line endings. WHAMMY. --- mp/src/game/server/physobj.cpp | 4286 ++++++++++++++++++++-------------------- 1 file changed, 2143 insertions(+), 2143 deletions(-) (limited to 'mp/src/game/server/physobj.cpp') diff --git a/mp/src/game/server/physobj.cpp b/mp/src/game/server/physobj.cpp index 9a48a76f..efaffd07 100644 --- a/mp/src/game/server/physobj.cpp +++ b/mp/src/game/server/physobj.cpp @@ -1,2143 +1,2143 @@ -//========= 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 ¶ms ); - 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(pEntity); - if ( pSpringEntity ) - { - IPhysicsObject *pRef = pSpringEntity->GetStartObject(); - pAttachOut[0] = pRef ? static_cast(pRef->GetGameData()) : NULL; - pAttachVPhysics[0] = pRef; - IPhysicsObject *pAttach = pSpringEntity->GetEndObject(); - pAttachOut[1] = pAttach ? static_cast(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( ¢er, 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 ¶ms ) -{ - // 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(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 ); -} +//========= 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 ¶ms ); + 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(pEntity); + if ( pSpringEntity ) + { + IPhysicsObject *pRef = pSpringEntity->GetStartObject(); + pAttachOut[0] = pRef ? static_cast(pRef->GetGameData()) : NULL; + pAttachVPhysics[0] = pRef; + IPhysicsObject *pAttach = pSpringEntity->GetEndObject(); + pAttachOut[1] = pAttach ? static_cast(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( ¢er, 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 ¶ms ) +{ + // 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(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 ); +} -- cgit v1.2.3