//========= Copyright © 1996-2005, 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" #ifdef PORTAL #include "portal_base2d_shared.h" #include "particle_system.h" #endif // PORTAL // 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_KEYFIELD( m_iExploitableByPlayer, FIELD_INTEGER, "ExploitableByPlayer" ), 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 ), DEFINE_INPUTFUNC( FIELD_VOID, "BecomeDebris", InputBecomeDebris ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable), // 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(); m_flDmgModFire = 1.0f; 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: Show and make solid //----------------------------------------------------------------------------- void CPhysBox::InputEnable( inputdata_t &inputdata ) { RemoveSolidFlags( FSOLID_NOT_SOLID ); RemoveEffects( EF_NODRAW ); } //----------------------------------------------------------------------------- // Purpose: Hide and make not solid //----------------------------------------------------------------------------- void CPhysBox::InputDisable( inputdata_t &inputdata ) { AddSolidFlags( FSOLID_NOT_SOLID ); AddEffects( EF_NODRAW ); } //----------------------------------------------------------------------------- // 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 ); } // Switch our collision to debris void CPhysBox::InputBecomeDebris( inputdata_t &inputdata ) { SetCollisionGroup( COLLISION_GROUP_DEBRIS ); } //----------------------------------------------------------------------------- // 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 // // --------------------------------------------------------------------- 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 ), DEFINE_INPUTFUNC( FIELD_VOID, "ExplodeAndRemove", InputExplodeAndRemove ), // 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 ); } void CPhysExplosion::InputExplodeAndRemove( inputdata_t &inputdata ) { ExplodeAndRemove( 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 ); } } } } } void CPhysExplosion::ExplodeAndRemove( CBaseEntity *pActivator, CBaseEntity *pCaller ) { Explode( pActivator, pCaller ); UTIL_Remove( this ); } //----------------------------------------------------------------------------- // 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; } //----------------------------------------------------------------------------- // Purpose: Create a CPhysExplosion entity on the fly and call the explode method. //----------------------------------------------------------------------------- void CreatePhysExplosion( Vector origin, float magnitude, float radius, string_t target, float innerRadius, int flags ) { CPhysExplosion *pExplosion = (CPhysExplosion*)CBaseEntity::Create( "env_physexplosion", origin, vec3_angle, NULL ); pExplosion->m_damage = magnitude; pExplosion->m_radius = radius; pExplosion->m_flInnerRadius = innerRadius; pExplosion->m_targetEntityName = target; pExplosion->AddSpawnFlags( flags ); variant_t emptyVariant; pExplosion->Spawn(); pExplosion->AcceptInput( "ExplodeAndRemove", NULL, NULL, emptyVariant, 0 ); } //================================================== // 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, 0, &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 ( !StringHasPrefixCaseSensitive( Studio_GetDefaultSurfaceProps( pOther->GetBaseAnimating()->GetModelPtr() ), "metal" ) ) 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 );