Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2143 lines
62 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "player.h"
  9. #include "vphysics_interface.h"
  10. #include "physics.h"
  11. #include "vcollide_parse.h"
  12. #include "entitylist.h"
  13. #include "physobj.h"
  14. #include "hierarchy.h"
  15. #include "game.h"
  16. #include "ndebugoverlay.h"
  17. #include "engine/IEngineSound.h"
  18. #include "model_types.h"
  19. #include "props.h"
  20. #include "physics_saverestore.h"
  21. #include "saverestore_utlvector.h"
  22. #include "vphysics/constraints.h"
  23. #include "collisionutils.h"
  24. #include "decals.h"
  25. #include "bone_setup.h"
  26. // memdbgon must be the last include file in a .cpp file!!!
  27. #include "tier0/memdbgon.h"
  28. ConVar debug_physimpact("debug_physimpact", "0" );
  29. const char *GetMassEquivalent(float flMass);
  30. // This is a physically simulated spring, used to join objects together and create spring forces
  31. //
  32. // NOTE: Springs are not physical in the sense that they only create force, they do not collide with
  33. // anything or have any REAL constraints. They can be stretched infinitely (though this will create
  34. // and infinite force), they can penetrate any other object (or spring). They do not occupy any space.
  35. //
  36. #define SF_SPRING_ONLYSTRETCH 0x0001
  37. class CPhysicsSpring : public CBaseEntity
  38. {
  39. DECLARE_CLASS( CPhysicsSpring, CBaseEntity );
  40. public:
  41. CPhysicsSpring();
  42. ~CPhysicsSpring();
  43. void Spawn( void );
  44. void Activate( void );
  45. // Inputs
  46. void InputSetSpringConstant( inputdata_t &inputdata );
  47. void InputSetSpringDamping( inputdata_t &inputdata );
  48. void InputSetSpringLength( inputdata_t &inputdata );
  49. // Debug
  50. int DrawDebugTextOverlays(void);
  51. void DrawDebugGeometryOverlays(void);
  52. void GetSpringObjectConnections( string_t nameStart, string_t nameEnd, IPhysicsObject **pStart, IPhysicsObject **pEnd );
  53. void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params );
  54. IPhysicsObject *GetStartObject() { return m_pSpring ? m_pSpring->GetStartObject() : NULL; }
  55. IPhysicsObject *GetEndObject() { return m_pSpring ? m_pSpring->GetEndObject() : NULL; }
  56. DECLARE_DATADESC();
  57. private:
  58. IPhysicsSpring *m_pSpring;
  59. bool m_isLocal;
  60. // These are "template" values used to construct the spring. After creation, they are not needed
  61. float m_tempConstant;
  62. float m_tempLength; // This is the "ideal" length of the spring, not the length it is currently stretched to.
  63. float m_tempDamping;
  64. float m_tempRelativeDamping;
  65. string_t m_nameAttachStart;
  66. string_t m_nameAttachEnd;
  67. Vector m_start;
  68. Vector m_end;
  69. unsigned int m_teleportTick;
  70. };
  71. LINK_ENTITY_TO_CLASS( phys_spring, CPhysicsSpring );
  72. BEGIN_DATADESC( CPhysicsSpring )
  73. DEFINE_PHYSPTR( m_pSpring ),
  74. DEFINE_KEYFIELD( m_tempConstant, FIELD_FLOAT, "constant" ),
  75. DEFINE_KEYFIELD( m_tempLength, FIELD_FLOAT, "length" ),
  76. DEFINE_KEYFIELD( m_tempDamping, FIELD_FLOAT, "damping" ),
  77. DEFINE_KEYFIELD( m_tempRelativeDamping, FIELD_FLOAT, "relativedamping" ),
  78. DEFINE_KEYFIELD( m_nameAttachStart, FIELD_STRING, "attach1" ),
  79. DEFINE_KEYFIELD( m_nameAttachEnd, FIELD_STRING, "attach2" ),
  80. DEFINE_FIELD( m_start, FIELD_POSITION_VECTOR ),
  81. DEFINE_KEYFIELD( m_end, FIELD_POSITION_VECTOR, "springaxis" ),
  82. DEFINE_FIELD( m_isLocal, FIELD_BOOLEAN ),
  83. // Not necessary to save... it's only there to make sure
  84. // DEFINE_FIELD( m_teleportTick, FIELD_INTEGER ),
  85. // Inputs
  86. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpringConstant", InputSetSpringConstant ),
  87. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpringLength", InputSetSpringLength ),
  88. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpringDamping", InputSetSpringDamping ),
  89. END_DATADESC()
  90. // debug function - slow, uses dynamic_cast<> - use this to query the attached objects
  91. // physics_debug_entity toggles the constraint system for an object using this
  92. bool GetSpringAttachments( CBaseEntity *pEntity, CBaseEntity *pAttachOut[2], IPhysicsObject *pAttachVPhysics[2] )
  93. {
  94. CPhysicsSpring *pSpringEntity = dynamic_cast<CPhysicsSpring *>(pEntity);
  95. if ( pSpringEntity )
  96. {
  97. IPhysicsObject *pRef = pSpringEntity->GetStartObject();
  98. pAttachOut[0] = pRef ? static_cast<CBaseEntity *>(pRef->GetGameData()) : NULL;
  99. pAttachVPhysics[0] = pRef;
  100. IPhysicsObject *pAttach = pSpringEntity->GetEndObject();
  101. pAttachOut[1] = pAttach ? static_cast<CBaseEntity *>(pAttach->GetGameData()) : NULL;
  102. pAttachVPhysics[1] = pAttach;
  103. return true;
  104. }
  105. return false;
  106. }
  107. CPhysicsSpring::CPhysicsSpring( void )
  108. {
  109. #ifdef _DEBUG
  110. m_start.Init();
  111. m_end.Init();
  112. #endif
  113. m_pSpring = NULL;
  114. m_tempConstant = 150;
  115. m_tempLength = 0;
  116. m_tempDamping = 2.0;
  117. m_tempRelativeDamping = 0.01;
  118. m_isLocal = false;
  119. m_teleportTick = 0xFFFFFFFF;
  120. }
  121. CPhysicsSpring::~CPhysicsSpring( void )
  122. {
  123. if ( m_pSpring )
  124. {
  125. physenv->DestroySpring( m_pSpring );
  126. }
  127. }
  128. //------------------------------------------------------------------------------
  129. // Purpose:
  130. //------------------------------------------------------------------------------
  131. void CPhysicsSpring::InputSetSpringConstant( inputdata_t &inputdata )
  132. {
  133. m_tempConstant = inputdata.value.Float();
  134. m_pSpring->SetSpringConstant(inputdata.value.Float());
  135. }
  136. //------------------------------------------------------------------------------
  137. // Purpose:
  138. //------------------------------------------------------------------------------
  139. void CPhysicsSpring::InputSetSpringDamping( inputdata_t &inputdata )
  140. {
  141. m_tempDamping = inputdata.value.Float();
  142. m_pSpring->SetSpringDamping(inputdata.value.Float());
  143. }
  144. //------------------------------------------------------------------------------
  145. // Purpose:
  146. //------------------------------------------------------------------------------
  147. void CPhysicsSpring::InputSetSpringLength( inputdata_t &inputdata )
  148. {
  149. m_tempLength = inputdata.value.Float();
  150. m_pSpring->SetSpringLength(inputdata.value.Float());
  151. }
  152. //-----------------------------------------------------------------------------
  153. // Purpose: Draw any debug text overlays
  154. // Output : Current text offset from the top
  155. //-----------------------------------------------------------------------------
  156. int CPhysicsSpring::DrawDebugTextOverlays(void)
  157. {
  158. int text_offset = BaseClass::DrawDebugTextOverlays();
  159. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  160. {
  161. char tempstr[512];
  162. Q_snprintf(tempstr,sizeof(tempstr),"Constant: %3.2f",m_tempConstant);
  163. EntityText(text_offset,tempstr,0);
  164. text_offset++;
  165. Q_snprintf(tempstr,sizeof(tempstr),"Length: %3.2f",m_tempLength);
  166. EntityText(text_offset,tempstr,0);
  167. text_offset++;
  168. Q_snprintf(tempstr,sizeof(tempstr),"Damping: %3.2f",m_tempDamping);
  169. EntityText(text_offset,tempstr,0);
  170. text_offset++;
  171. }
  172. return text_offset;
  173. }
  174. //-----------------------------------------------------------------------------
  175. // Purpose: Override base class to add display of fly direction
  176. // Input :
  177. // Output :
  178. //-----------------------------------------------------------------------------
  179. void CPhysicsSpring::DrawDebugGeometryOverlays(void)
  180. {
  181. if ( !m_pSpring )
  182. return;
  183. // ------------------------------
  184. // Draw if BBOX is on
  185. // ------------------------------
  186. if (m_debugOverlays & OVERLAY_BBOX_BIT)
  187. {
  188. Vector vStartPos;
  189. Vector vEndPos;
  190. m_pSpring->GetEndpoints( &vStartPos, &vEndPos );
  191. Vector vSpringDir = vEndPos - vStartPos;
  192. VectorNormalize(vSpringDir);
  193. Vector vLength = vStartPos + (vSpringDir*m_tempLength);
  194. NDebugOverlay::Line(vStartPos, vLength, 0,0,255, false, 0);
  195. NDebugOverlay::Line(vLength, vEndPos, 255,0,0, false, 0);
  196. }
  197. BaseClass::DrawDebugGeometryOverlays();
  198. }
  199. bool PointIsNearer( IPhysicsObject *pObject1, const Vector &point1, const Vector &point2 )
  200. {
  201. Vector center;
  202. pObject1->GetPosition( &center, 0 );
  203. float dist1 = (center - point1).LengthSqr();
  204. float dist2 = (center - point2).LengthSqr();
  205. if ( dist1 < dist2 )
  206. return true;
  207. return false;
  208. }
  209. void CPhysicsSpring::GetSpringObjectConnections( string_t nameStart, string_t nameEnd, IPhysicsObject **pStart, IPhysicsObject **pEnd )
  210. {
  211. IPhysicsObject *pStartObject = FindPhysicsObjectByName( STRING(nameStart), this );
  212. IPhysicsObject *pEndObject = FindPhysicsObjectByName( STRING(nameEnd), this );
  213. // Assume the world for missing objects
  214. if ( !pStartObject )
  215. {
  216. pStartObject = g_PhysWorldObject;
  217. }
  218. else if ( !pEndObject )
  219. {
  220. // try to sort so that the world is always the start object
  221. pEndObject = pStartObject;
  222. pStartObject = g_PhysWorldObject;
  223. }
  224. else
  225. {
  226. CBaseEntity *pEntity0 = (CBaseEntity *) (pStartObject->GetGameData());
  227. if ( pEntity0 )
  228. {
  229. g_pNotify->AddEntity( this, pEntity0 );
  230. }
  231. CBaseEntity *pEntity1 = (CBaseEntity *) pEndObject->GetGameData();
  232. if ( pEntity1 )
  233. {
  234. g_pNotify->AddEntity( this, pEntity1 );
  235. }
  236. }
  237. *pStart = pStartObject;
  238. *pEnd = pEndObject;
  239. }
  240. void CPhysicsSpring::Activate( void )
  241. {
  242. BaseClass::Activate();
  243. // UNDONE: save/restore all data, and only create the spring here
  244. if ( !m_pSpring )
  245. {
  246. IPhysicsObject *pStart, *pEnd;
  247. GetSpringObjectConnections( m_nameAttachStart, m_nameAttachEnd, &pStart, &pEnd );
  248. // Needs to connect to real, different objects
  249. if ( (!pStart || !pEnd) || (pStart == pEnd) )
  250. {
  251. DevMsg("ERROR: Can't init spring %s from \"%s\" to \"%s\"\n", GetDebugName(), STRING(m_nameAttachStart), STRING(m_nameAttachEnd) );
  252. UTIL_Remove( this );
  253. return;
  254. }
  255. // if m_end is not closer to pEnd than m_start, swap
  256. if ( !PointIsNearer( pEnd, m_end, m_start ) )
  257. {
  258. Vector tmpVec = m_start;
  259. m_start = m_end;
  260. m_end = tmpVec;
  261. }
  262. // create the spring
  263. springparams_t spring;
  264. spring.constant = m_tempConstant;
  265. spring.damping = m_tempDamping;
  266. spring.naturalLength = m_tempLength;
  267. spring.relativeDamping = m_tempRelativeDamping;
  268. spring.startPosition = m_start;
  269. spring.endPosition = m_end;
  270. spring.useLocalPositions = false;
  271. spring.onlyStretch = HasSpawnFlags( SF_SPRING_ONLYSTRETCH );
  272. m_pSpring = physenv->CreateSpring( pStart, pEnd, &spring );
  273. }
  274. }
  275. void CPhysicsSpring::Spawn( void )
  276. {
  277. SetSolid( SOLID_NONE );
  278. m_start = GetAbsOrigin();
  279. if ( m_tempLength <= 0 )
  280. {
  281. m_tempLength = (m_end - m_start).Length();
  282. }
  283. }
  284. void CPhysicsSpring::NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t &params )
  285. {
  286. // don't recurse
  287. if ( eventType != NOTIFY_EVENT_TELEPORT || (unsigned int)gpGlobals->tickcount == m_teleportTick )
  288. return;
  289. m_teleportTick = gpGlobals->tickcount;
  290. PhysTeleportConstrainedEntity( pNotify, m_pSpring->GetStartObject(), m_pSpring->GetEndObject(), params.pTeleport->prevOrigin, params.pTeleport->prevAngles, params.pTeleport->physicsRotate );
  291. }
  292. // ---------------------------------------------------------------------
  293. //
  294. // CPhysBox -- physically simulated brush
  295. //
  296. // ---------------------------------------------------------------------
  297. // SendTable stuff.
  298. IMPLEMENT_SERVERCLASS_ST(CPhysBox, DT_PhysBox)
  299. END_SEND_TABLE()
  300. LINK_ENTITY_TO_CLASS( func_physbox, CPhysBox );
  301. BEGIN_DATADESC( CPhysBox )
  302. DEFINE_FIELD( m_hCarryingPlayer, FIELD_EHANDLE ),
  303. DEFINE_KEYFIELD( m_massScale, FIELD_FLOAT, "massScale" ),
  304. DEFINE_KEYFIELD( m_damageType, FIELD_INTEGER, "Damagetype" ),
  305. DEFINE_KEYFIELD( m_iszOverrideScript, FIELD_STRING, "overridescript" ),
  306. DEFINE_KEYFIELD( m_damageToEnableMotion, FIELD_INTEGER, "damagetoenablemotion" ),
  307. DEFINE_KEYFIELD( m_flForceToEnableMotion, FIELD_FLOAT, "forcetoenablemotion" ),
  308. DEFINE_KEYFIELD( m_angPreferredCarryAngles, FIELD_VECTOR, "preferredcarryangles" ),
  309. DEFINE_KEYFIELD( m_bNotSolidToWorld, FIELD_BOOLEAN, "notsolid" ),
  310. DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ),
  311. DEFINE_INPUTFUNC( FIELD_VOID, "Sleep", InputSleep ),
  312. DEFINE_INPUTFUNC( FIELD_VOID, "EnableMotion", InputEnableMotion ),
  313. DEFINE_INPUTFUNC( FIELD_VOID, "DisableMotion", InputDisableMotion ),
  314. DEFINE_INPUTFUNC( FIELD_VOID, "ForceDrop", InputForceDrop ),
  315. DEFINE_INPUTFUNC( FIELD_VOID, "DisableFloating", InputDisableFloating ),
  316. // Function pointers
  317. DEFINE_ENTITYFUNC( BreakTouch ),
  318. // Outputs
  319. DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ),
  320. DEFINE_OUTPUT( m_OnAwakened, "OnAwakened" ),
  321. DEFINE_OUTPUT( m_OnMotionEnabled, "OnMotionEnabled" ),
  322. DEFINE_OUTPUT( m_OnPhysGunPickup, "OnPhysGunPickup" ),
  323. DEFINE_OUTPUT( m_OnPhysGunPunt, "OnPhysGunPunt" ),
  324. DEFINE_OUTPUT( m_OnPhysGunOnlyPickup, "OnPhysGunOnlyPickup" ),
  325. DEFINE_OUTPUT( m_OnPhysGunDrop, "OnPhysGunDrop" ),
  326. DEFINE_OUTPUT( m_OnPlayerUse, "OnPlayerUse" ),
  327. END_DATADESC()
  328. // UNDONE: Save/Restore needs to take the physics object's properties into account
  329. // UNDONE: Acceleration, velocity, angular velocity, etc. must be preserved
  330. // UNDONE: Many of these quantities are relative to a coordinate frame
  331. // UNDONE: Translate when going across transitions?
  332. // UNDONE: Build transition transformation, and transform data in save/restore for IPhysicsObject
  333. // UNDONE: Angles are saved in the entity, but not propagated back to the IPhysicsObject on restore
  334. //-----------------------------------------------------------------------------
  335. // Purpose:
  336. //-----------------------------------------------------------------------------
  337. void CPhysBox::Spawn( void )
  338. {
  339. // Initialize damage modifiers. Must be done before baseclass spawn.
  340. m_flDmgModBullet = func_breakdmg_bullet.GetFloat();
  341. m_flDmgModClub = func_breakdmg_club.GetFloat();
  342. m_flDmgModExplosive = func_breakdmg_explosive.GetFloat();
  343. ParsePropData();
  344. Precache();
  345. m_iMaxHealth = ( m_iHealth > 0 ) ? m_iHealth : 1;
  346. if ( HasSpawnFlags( SF_BREAK_TRIGGER_ONLY ) )
  347. {
  348. m_takedamage = DAMAGE_EVENTS_ONLY;
  349. AddSpawnFlags( SF_BREAK_DONT_TAKE_PHYSICS_DAMAGE );
  350. }
  351. else if ( m_iHealth == 0 )
  352. {
  353. m_takedamage = DAMAGE_EVENTS_ONLY;
  354. AddSpawnFlags( SF_BREAK_DONT_TAKE_PHYSICS_DAMAGE );
  355. }
  356. else
  357. {
  358. m_takedamage = DAMAGE_YES;
  359. }
  360. SetMoveType( MOVETYPE_NONE );
  361. SetAbsVelocity( vec3_origin );
  362. SetModel( STRING( GetModelName() ) );
  363. SetSolid( SOLID_VPHYSICS );
  364. if ( HasSpawnFlags( SF_PHYSBOX_DEBRIS ) )
  365. {
  366. SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  367. }
  368. if ( HasSpawnFlags( SF_PHYSBOX_NO_ROTORWASH_PUSH ) )
  369. {
  370. AddEFlags( EFL_NO_ROTORWASH_PUSH );
  371. }
  372. if ( m_bNotSolidToWorld )
  373. {
  374. AddSolidFlags( FSOLID_NOT_SOLID );
  375. }
  376. CreateVPhysics();
  377. m_hCarryingPlayer = NULL;
  378. SetTouch( &CPhysBox::BreakTouch );
  379. if ( HasSpawnFlags( SF_BREAK_TRIGGER_ONLY ) ) // Only break on trigger
  380. {
  381. SetTouch( NULL );
  382. }
  383. if ( m_impactEnergyScale == 0 )
  384. {
  385. m_impactEnergyScale = 1.0;
  386. }
  387. }
  388. // shared from studiomdl, checks for long, thin objects and adds some damping
  389. // to prevent endless rolling due to low inertia
  390. static bool ShouldDampRotation( const CPhysCollide *pCollide )
  391. {
  392. Vector mins, maxs;
  393. physcollision->CollideGetAABB( &mins, &maxs, pCollide, vec3_origin, vec3_angle );
  394. Vector size = maxs-mins;
  395. int largest = 0;
  396. float largeSize = size[0];
  397. for ( int i = 1; i < 3; i++ )
  398. {
  399. if ( size[i] > largeSize )
  400. {
  401. largeSize = size[i];
  402. largest = i;
  403. }
  404. }
  405. size[largest] = 0;
  406. float len = size.Length();
  407. if ( len > 0 )
  408. {
  409. float sizeRatio = largeSize / len;
  410. // HACKHACK: Hardcoded size ratio to induce damping
  411. // This prevents long skinny objects from rolling endlessly
  412. if ( sizeRatio > 9 )
  413. return true;
  414. }
  415. return false;
  416. }
  417. bool CPhysBox::CreateVPhysics()
  418. {
  419. solid_t tmpSolid;
  420. PhysModelParseSolid( tmpSolid, this, GetModelIndex() );
  421. if ( m_massScale > 0 )
  422. {
  423. tmpSolid.params.mass *= m_massScale;
  424. }
  425. vcollide_t *pVCollide = modelinfo->GetVCollide( GetModelIndex() );
  426. PhysGetMassCenterOverride( this, pVCollide, tmpSolid );
  427. PhysSolidOverride( tmpSolid, m_iszOverrideScript );
  428. if ( tmpSolid.params.rotdamping < 1.0f && ShouldDampRotation(pVCollide->solids[0]) )
  429. {
  430. tmpSolid.params.rotdamping = 1.0f;
  431. }
  432. IPhysicsObject *pPhysics = VPhysicsInitNormal( GetSolid(), GetSolidFlags(), true, &tmpSolid );
  433. if ( m_damageType == 1 )
  434. {
  435. PhysSetGameFlags( pPhysics, FVPHYSICS_DMG_SLICE );
  436. }
  437. // Wake it up if not asleep
  438. if ( !HasSpawnFlags(SF_PHYSBOX_ASLEEP) )
  439. {
  440. pPhysics->Wake();
  441. }
  442. if ( HasSpawnFlags(SF_PHYSBOX_MOTIONDISABLED) || m_damageToEnableMotion > 0 || m_flForceToEnableMotion > 0 )
  443. {
  444. pPhysics->EnableMotion( false );
  445. }
  446. return true;
  447. }
  448. //-----------------------------------------------------------------------------
  449. // Purpose:
  450. //-----------------------------------------------------------------------------
  451. int CPhysBox::ObjectCaps()
  452. {
  453. int caps = BaseClass::ObjectCaps() | FCAP_WCEDIT_POSITION;
  454. if ( HasSpawnFlags( SF_PHYSBOX_ENABLE_PICKUP_OUTPUT ) )
  455. {
  456. caps |= FCAP_IMPULSE_USE;
  457. }
  458. else if ( !HasSpawnFlags( SF_PHYSBOX_IGNOREUSE ) )
  459. {
  460. if ( CBasePlayer::CanPickupObject( this, 35, 128 ) )
  461. {
  462. caps |= FCAP_IMPULSE_USE;
  463. }
  464. }
  465. return caps;
  466. }
  467. //-----------------------------------------------------------------------------
  468. // Purpose:
  469. //-----------------------------------------------------------------------------
  470. void CPhysBox::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  471. {
  472. CBasePlayer *pPlayer = ToBasePlayer( pActivator );
  473. if ( pPlayer )
  474. {
  475. if ( HasSpawnFlags( SF_PHYSBOX_ENABLE_PICKUP_OUTPUT ) )
  476. {
  477. m_OnPlayerUse.FireOutput( this, this );
  478. }
  479. if ( !HasSpawnFlags( SF_PHYSBOX_IGNOREUSE ) )
  480. {
  481. pPlayer->PickupObject( this );
  482. }
  483. }
  484. }
  485. //-----------------------------------------------------------------------------
  486. //-----------------------------------------------------------------------------
  487. bool CPhysBox::CanBePickedUpByPhyscannon()
  488. {
  489. if ( HasSpawnFlags( SF_PHYSBOX_NEVER_PICK_UP ) )
  490. return false;
  491. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  492. if ( !pPhysicsObject )
  493. return false;
  494. if ( !pPhysicsObject->IsMotionEnabled() && !HasSpawnFlags( SF_PHYSBOX_ENABLE_ON_PHYSCANNON ) )
  495. return false;
  496. return true;
  497. }
  498. //-----------------------------------------------------------------------------
  499. // Purpose: Draw any debug text overlays
  500. // Output : Current text offset from the top
  501. //-----------------------------------------------------------------------------
  502. int CPhysBox::DrawDebugTextOverlays(void)
  503. {
  504. int text_offset = BaseClass::DrawDebugTextOverlays();
  505. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  506. {
  507. if (VPhysicsGetObject())
  508. {
  509. char tempstr[512];
  510. Q_snprintf(tempstr, sizeof(tempstr),"Mass: %.2f kg / %.2f lb (%s)", VPhysicsGetObject()->GetMass(), kg2lbs(VPhysicsGetObject()->GetMass()), GetMassEquivalent(VPhysicsGetObject()->GetMass()));
  511. EntityText( text_offset, tempstr, 0);
  512. text_offset++;
  513. }
  514. }
  515. return text_offset;
  516. }
  517. //-----------------------------------------------------------------------------
  518. // Purpose: Input handler that breaks the physics object away from its parent
  519. // and starts it simulating.
  520. //-----------------------------------------------------------------------------
  521. void CPhysBox::InputWake( inputdata_t &inputdata )
  522. {
  523. VPhysicsGetObject()->Wake();
  524. }
  525. //-----------------------------------------------------------------------------
  526. // Purpose: Input handler that breaks the physics object away from its parent
  527. // and stops it simulating.
  528. //-----------------------------------------------------------------------------
  529. void CPhysBox::InputSleep( inputdata_t &inputdata )
  530. {
  531. VPhysicsGetObject()->Sleep();
  532. }
  533. //-----------------------------------------------------------------------------
  534. // Purpose: Enable physics motion and collision response (on by default)
  535. //-----------------------------------------------------------------------------
  536. void CPhysBox::InputEnableMotion( inputdata_t &inputdata )
  537. {
  538. EnableMotion();
  539. }
  540. //-----------------------------------------------------------------------------
  541. // Purpose:
  542. //-----------------------------------------------------------------------------
  543. void CPhysBox::EnableMotion( void )
  544. {
  545. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  546. if ( pPhysicsObject != NULL )
  547. {
  548. pPhysicsObject->EnableMotion( true );
  549. pPhysicsObject->Wake();
  550. }
  551. m_damageToEnableMotion = 0;
  552. m_flForceToEnableMotion = 0;
  553. m_OnMotionEnabled.FireOutput( this, this, 0 );
  554. }
  555. //-----------------------------------------------------------------------------
  556. // Purpose: Disable any physics motion or collision response
  557. //-----------------------------------------------------------------------------
  558. void CPhysBox::InputDisableMotion( inputdata_t &inputdata )
  559. {
  560. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  561. if ( pPhysicsObject != NULL )
  562. {
  563. pPhysicsObject->EnableMotion( false );
  564. }
  565. }
  566. // Turn off floating simulation (and cost)
  567. void CPhysBox::InputDisableFloating( inputdata_t &inputdata )
  568. {
  569. PhysEnableFloating( VPhysicsGetObject(), false );
  570. }
  571. //-----------------------------------------------------------------------------
  572. // Purpose: If we're being held by the player's hand/physgun, force it to drop us
  573. //-----------------------------------------------------------------------------
  574. void CPhysBox::InputForceDrop( inputdata_t &inputdata )
  575. {
  576. if ( m_hCarryingPlayer )
  577. {
  578. m_hCarryingPlayer->ForceDropOfCarriedPhysObjects();
  579. }
  580. }
  581. //-----------------------------------------------------------------------------
  582. // Purpose:
  583. //-----------------------------------------------------------------------------
  584. void CPhysBox::Move( const Vector &direction )
  585. {
  586. VPhysicsGetObject()->ApplyForceCenter( direction );
  587. }
  588. // Update the visible representation of the physic system's representation of this object
  589. void CPhysBox::VPhysicsUpdate( IPhysicsObject *pPhysics )
  590. {
  591. BaseClass::VPhysicsUpdate( pPhysics );
  592. // if this is the first time we have moved, fire our target
  593. if ( HasSpawnFlags( SF_PHYSBOX_ASLEEP ) )
  594. {
  595. if ( !pPhysics->IsAsleep() )
  596. {
  597. m_OnAwakened.FireOutput(this, this);
  598. FireTargets( STRING(m_target), this, this, USE_TOGGLE, 0 );
  599. RemoveSpawnFlags( SF_PHYSBOX_ASLEEP );
  600. }
  601. }
  602. }
  603. //-----------------------------------------------------------------------------
  604. // Purpose:
  605. //-----------------------------------------------------------------------------
  606. void CPhysBox::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  607. {
  608. if ( reason == PUNTED_BY_CANNON )
  609. {
  610. m_OnPhysGunPunt.FireOutput( pPhysGunUser, this );
  611. }
  612. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  613. if ( pPhysicsObject && !pPhysicsObject->IsMoveable() )
  614. {
  615. if ( !HasSpawnFlags( SF_PHYSBOX_ENABLE_ON_PHYSCANNON ) )
  616. return;
  617. EnableMotion();
  618. }
  619. m_OnPhysGunPickup.FireOutput( pPhysGunUser, this );
  620. // Are we just being punted?
  621. if ( reason == PUNTED_BY_CANNON )
  622. return;
  623. if( reason == PICKED_UP_BY_CANNON )
  624. {
  625. m_OnPhysGunOnlyPickup.FireOutput( pPhysGunUser, this );
  626. }
  627. m_hCarryingPlayer = pPhysGunUser;
  628. }
  629. //-----------------------------------------------------------------------------
  630. // Purpose:
  631. //-----------------------------------------------------------------------------
  632. void CPhysBox::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
  633. {
  634. BaseClass::OnPhysGunDrop( pPhysGunUser, Reason );
  635. m_hCarryingPlayer = NULL;
  636. m_OnPhysGunDrop.FireOutput( pPhysGunUser, this );
  637. }
  638. //-----------------------------------------------------------------------------
  639. // Purpose:
  640. //-----------------------------------------------------------------------------
  641. void CPhysBox::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  642. {
  643. BaseClass::VPhysicsCollision( index, pEvent );
  644. IPhysicsObject *pPhysObj = pEvent->pObjects[!index];
  645. // If we have a force to enable motion, and we're still disabled, check to see if this should enable us
  646. if ( m_flForceToEnableMotion )
  647. {
  648. CBaseEntity *pOther = static_cast<CBaseEntity *>(pPhysObj->GetGameData());
  649. // Don't allow the player to bump an object active if we've requested not to
  650. if ( ( pOther && pOther->IsPlayer() && HasSpawnFlags( SF_PHYSBOX_PREVENT_PLAYER_TOUCH_ENABLE ) ) == false )
  651. {
  652. // Large enough to enable motion?
  653. float flForce = pEvent->collisionSpeed * pEvent->pObjects[!index]->GetMass();
  654. if ( flForce >= m_flForceToEnableMotion )
  655. {
  656. EnableMotion();
  657. }
  658. }
  659. }
  660. }
  661. //-----------------------------------------------------------------------------
  662. // Purpose:
  663. //-----------------------------------------------------------------------------
  664. int CPhysBox::OnTakeDamage( const CTakeDamageInfo &info )
  665. {
  666. if ( IsMarkedForDeletion() )
  667. return 0;
  668. // note: if motion is disabled, OnTakeDamage can't apply physics force
  669. int ret = BaseClass::OnTakeDamage( info );
  670. if ( info.GetInflictor() )
  671. {
  672. m_OnDamaged.FireOutput( info.GetAttacker(), this );
  673. }
  674. // Have we been broken? If so, abort
  675. if ( GetHealth() <= 0 )
  676. return ret;
  677. // If we have a force to enable motion, and we're still disabled, check to see if this should enable us
  678. if ( m_flForceToEnableMotion )
  679. {
  680. // Large enough to enable motion?
  681. float flForce = info.GetDamageForce().Length();
  682. if ( flForce >= m_flForceToEnableMotion )
  683. {
  684. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  685. if ( pPhysicsObject )
  686. {
  687. EnableMotion();
  688. }
  689. }
  690. }
  691. // Check our health against the threshold:
  692. if( m_damageToEnableMotion > 0 && GetHealth() < m_damageToEnableMotion )
  693. {
  694. EnableMotion();
  695. VPhysicsTakeDamage( info );
  696. }
  697. return ret;
  698. }
  699. //-----------------------------------------------------------------------------
  700. // Purpose: Return true if this physbox has preferred carry angles
  701. //-----------------------------------------------------------------------------
  702. bool CPhysBox::HasPreferredCarryAnglesForPlayer( CBasePlayer *pPlayer )
  703. {
  704. return HasSpawnFlags( SF_PHYSBOX_USEPREFERRED );
  705. }
  706. // ---------------------------------------------------------------------
  707. //
  708. // CPhysExplosion -- physically simulated explosion
  709. //
  710. // ---------------------------------------------------------------------
  711. #define SF_PHYSEXPLOSION_NODAMAGE 0x0001
  712. #define SF_PHYSEXPLOSION_PUSH_PLAYER 0x0002
  713. #define SF_PHYSEXPLOSION_RADIAL 0x0004
  714. #define SF_PHYSEXPLOSION_TEST_LOS 0x0008
  715. #define SF_PHYSEXPLOSION_DISORIENT_PLAYER 0x0010
  716. LINK_ENTITY_TO_CLASS( env_physexplosion, CPhysExplosion );
  717. BEGIN_DATADESC( CPhysExplosion )
  718. DEFINE_KEYFIELD( m_damage, FIELD_FLOAT, "magnitude" ),
  719. DEFINE_KEYFIELD( m_radius, FIELD_FLOAT, "radius" ),
  720. DEFINE_KEYFIELD( m_targetEntityName, FIELD_STRING, "targetentityname" ),
  721. DEFINE_KEYFIELD( m_flInnerRadius, FIELD_FLOAT, "inner_radius" ),
  722. // Inputs
  723. DEFINE_INPUTFUNC( FIELD_VOID, "Explode", InputExplode ),
  724. // Outputs
  725. DEFINE_OUTPUT( m_OnPushedPlayer, "OnPushedPlayer" ),
  726. END_DATADESC()
  727. void CPhysExplosion::Spawn( void )
  728. {
  729. SetMoveType( MOVETYPE_NONE );
  730. SetSolid( SOLID_NONE );
  731. SetModelName( NULL_STRING );
  732. }
  733. float CPhysExplosion::GetRadius( void )
  734. {
  735. float radius = m_radius;
  736. if ( radius <= 0 )
  737. {
  738. // Use the same radius as combat
  739. radius = m_damage * 2.5;
  740. }
  741. return radius;
  742. }
  743. CBaseEntity *CPhysExplosion::FindEntity( CBaseEntity *pEntity, CBaseEntity *pActivator, CBaseEntity *pCaller )
  744. {
  745. // Filter by name or classname
  746. if ( m_targetEntityName != NULL_STRING )
  747. {
  748. // Try an explicit name first
  749. CBaseEntity *pTarget = gEntList.FindEntityByName( pEntity, m_targetEntityName, NULL, pActivator, pCaller );
  750. if ( pTarget != NULL )
  751. return pTarget;
  752. // Failing that, try a classname
  753. return gEntList.FindEntityByClassnameWithin( pEntity, STRING(m_targetEntityName), GetAbsOrigin(), GetRadius() );
  754. }
  755. // Just find anything in the radius
  756. return gEntList.FindEntityInSphere( pEntity, GetAbsOrigin(), GetRadius() );
  757. }
  758. //-----------------------------------------------------------------------------
  759. // Purpose:
  760. //-----------------------------------------------------------------------------
  761. void CPhysExplosion::InputExplode( inputdata_t &inputdata )
  762. {
  763. Explode( inputdata.pActivator, inputdata.pCaller );
  764. }
  765. //-----------------------------------------------------------------------------
  766. // Purpose:
  767. //-----------------------------------------------------------------------------
  768. void CPhysExplosion::Explode( CBaseEntity *pActivator, CBaseEntity *pCaller )
  769. {
  770. CBaseEntity *pEntity = NULL;
  771. float adjustedDamage, falloff, flDist;
  772. Vector vecSpot, vecOrigin;
  773. falloff = 1.0 / 2.5;
  774. // iterate on all entities in the vicinity.
  775. // I've removed the traceline heuristic from phys explosions. SO right now they will
  776. // affect entities through walls. (sjb)
  777. // UNDONE: Try tracing world-only?
  778. while ((pEntity = FindEntity( pEntity, pActivator, pCaller )) != NULL)
  779. {
  780. // UNDONE: Ask the object if it should get force if it's not MOVETYPE_VPHYSICS?
  781. if ( pEntity->m_takedamage != DAMAGE_NO && (pEntity->GetMoveType() == MOVETYPE_VPHYSICS || (pEntity->VPhysicsGetObject() /*&& !pEntity->IsPlayer()*/)) )
  782. {
  783. vecOrigin = GetAbsOrigin();
  784. vecSpot = pEntity->BodyTarget( vecOrigin );
  785. // Squash this down to a circle
  786. if ( HasSpawnFlags( SF_PHYSEXPLOSION_RADIAL ) )
  787. {
  788. vecOrigin[2] = vecSpot[2];
  789. }
  790. // decrease damage for an ent that's farther from the bomb.
  791. flDist = ( vecOrigin - vecSpot ).Length();
  792. if( m_radius == 0 || flDist <= m_radius )
  793. {
  794. if ( HasSpawnFlags( SF_PHYSEXPLOSION_TEST_LOS ) )
  795. {
  796. Vector vecStartPos = GetAbsOrigin();
  797. Vector vecEndPos = pEntity->BodyTarget( vecStartPos, false );
  798. if ( m_flInnerRadius != 0.0f )
  799. {
  800. // Find a point on our inner radius sphere to begin from
  801. Vector vecDirToTarget = ( vecEndPos - vecStartPos );
  802. VectorNormalize( vecDirToTarget );
  803. vecStartPos = GetAbsOrigin() + ( vecDirToTarget * m_flInnerRadius );
  804. }
  805. trace_t tr;
  806. UTIL_TraceLine( vecStartPos,
  807. pEntity->BodyTarget( vecStartPos, false ),
  808. MASK_SOLID_BRUSHONLY,
  809. this,
  810. COLLISION_GROUP_NONE,
  811. &tr );
  812. // Shielded
  813. if ( tr.fraction < 1.0f && tr.m_pEnt != pEntity )
  814. continue;
  815. }
  816. adjustedDamage = flDist * falloff;
  817. adjustedDamage = m_damage - adjustedDamage;
  818. if ( adjustedDamage < 1 )
  819. {
  820. adjustedDamage = 1;
  821. }
  822. CTakeDamageInfo info( this, this, adjustedDamage, DMG_BLAST );
  823. CalculateExplosiveDamageForce( &info, (vecSpot - vecOrigin), vecOrigin );
  824. if ( HasSpawnFlags( SF_PHYSEXPLOSION_PUSH_PLAYER ) )
  825. {
  826. if ( pEntity->IsPlayer() )
  827. {
  828. Vector vecPushDir = ( pEntity->BodyTarget( GetAbsOrigin(), false ) - GetAbsOrigin() );
  829. float dist = VectorNormalize( vecPushDir );
  830. float flFalloff = RemapValClamped( dist, m_radius, m_radius*0.75f, 0.0f, 1.0f );
  831. if ( HasSpawnFlags( SF_PHYSEXPLOSION_DISORIENT_PLAYER ) )
  832. {
  833. //Disorient the player
  834. QAngle vecDeltaAngles;
  835. vecDeltaAngles.x = random->RandomInt( -30, 30 );
  836. vecDeltaAngles.y = random->RandomInt( -30, 30 );
  837. vecDeltaAngles.z = 0.0f;
  838. CBasePlayer *pPlayer = ToBasePlayer( pEntity );
  839. pPlayer->SnapEyeAngles( GetLocalAngles() + vecDeltaAngles );
  840. pEntity->ViewPunch( vecDeltaAngles );
  841. }
  842. Vector vecPush = (vecPushDir*m_damage*flFalloff*2.0f);
  843. if ( pEntity->GetFlags() & FL_BASEVELOCITY )
  844. {
  845. vecPush = vecPush + pEntity->GetBaseVelocity();
  846. }
  847. if ( vecPush.z > 0 && (pEntity->GetFlags() & FL_ONGROUND) )
  848. {
  849. pEntity->SetGroundEntity( NULL );
  850. Vector origin = pEntity->GetAbsOrigin();
  851. origin.z += 1.0f;
  852. pEntity->SetAbsOrigin( origin );
  853. }
  854. pEntity->SetBaseVelocity( vecPush );
  855. pEntity->AddFlag( FL_BASEVELOCITY );
  856. // Fire an output that the player has been pushed
  857. m_OnPushedPlayer.FireOutput( this, this );
  858. continue;
  859. }
  860. }
  861. if ( HasSpawnFlags( SF_PHYSEXPLOSION_NODAMAGE ) )
  862. {
  863. pEntity->VPhysicsTakeDamage( info );
  864. }
  865. else
  866. {
  867. pEntity->TakeDamage( info );
  868. }
  869. }
  870. }
  871. }
  872. }
  873. //-----------------------------------------------------------------------------
  874. // Purpose: Draw any debug text overlays
  875. // Output : Current text offset from the top
  876. //-----------------------------------------------------------------------------
  877. int CPhysExplosion::DrawDebugTextOverlays( void )
  878. {
  879. int text_offset = BaseClass::DrawDebugTextOverlays();
  880. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  881. {
  882. char tempstr[512];
  883. // print magnitude
  884. Q_snprintf(tempstr,sizeof(tempstr)," magnitude: %f", m_damage);
  885. EntityText(text_offset,tempstr,0);
  886. text_offset++;
  887. // print target entity
  888. Q_snprintf(tempstr,sizeof(tempstr)," limit to: %s", STRING(m_targetEntityName));
  889. EntityText(text_offset,tempstr,0);
  890. text_offset++;
  891. }
  892. return text_offset;
  893. }
  894. //==================================================
  895. // CPhysImpact
  896. //==================================================
  897. #define bitsPHYSIMPACT_NOFALLOFF 0x00000001
  898. #define bitsPHYSIMPACT_INFINITE_LENGTH 0x00000002
  899. #define bitsPHYSIMPACT_IGNORE_MASS 0x00000004
  900. #define bitsPHYSIMPACT_IGNORE_NORMAL 0x00000008
  901. #define DEFAULT_EXPLODE_DISTANCE 256
  902. LINK_ENTITY_TO_CLASS( env_physimpact, CPhysImpact );
  903. BEGIN_DATADESC( CPhysImpact )
  904. DEFINE_KEYFIELD( m_damage, FIELD_FLOAT, "magnitude" ),
  905. DEFINE_KEYFIELD( m_distance, FIELD_FLOAT, "distance" ),
  906. DEFINE_KEYFIELD( m_directionEntityName,FIELD_STRING, "directionentityname" ),
  907. // Function pointers
  908. DEFINE_FUNCTION( PointAtEntity ),
  909. DEFINE_INPUTFUNC( FIELD_VOID, "Impact", InputImpact ),
  910. END_DATADESC()
  911. //-----------------------------------------------------------------------------
  912. // Purpose:
  913. //-----------------------------------------------------------------------------
  914. void CPhysImpact::Activate( void )
  915. {
  916. BaseClass::Activate();
  917. }
  918. //-----------------------------------------------------------------------------
  919. // Purpose:
  920. //-----------------------------------------------------------------------------
  921. void CPhysImpact::Spawn( void )
  922. {
  923. SetMoveType( MOVETYPE_NONE );
  924. SetSolid( SOLID_NONE );
  925. SetModelName( NULL_STRING );
  926. //If not targetted, and no distance is set, give it a default value
  927. if ( m_distance == 0 )
  928. {
  929. m_distance = DEFAULT_EXPLODE_DISTANCE;
  930. }
  931. }
  932. //-----------------------------------------------------------------------------
  933. // Purpose:
  934. //-----------------------------------------------------------------------------
  935. void CPhysImpact::PointAtEntity( void )
  936. {
  937. //If we're not targetted at anything, don't bother
  938. if ( m_directionEntityName == NULL_STRING )
  939. return;
  940. UTIL_PointAtNamedEntity( this, m_directionEntityName );
  941. }
  942. //-----------------------------------------------------------------------------
  943. // Purpose:
  944. // Input : *pActivator -
  945. // *pCaller -
  946. // useType -
  947. // value -
  948. //-----------------------------------------------------------------------------
  949. void CPhysImpact::InputImpact( inputdata_t &inputdata )
  950. {
  951. Vector dir;
  952. trace_t trace;
  953. //If we have a direction target, setup to point at it
  954. if ( m_directionEntityName != NULL_STRING )
  955. {
  956. PointAtEntity();
  957. }
  958. AngleVectors( GetAbsAngles(), &dir );
  959. //Setup our trace information
  960. float dist = HasSpawnFlags( bitsPHYSIMPACT_INFINITE_LENGTH ) ? MAX_TRACE_LENGTH : m_distance;
  961. Vector start = GetAbsOrigin();
  962. Vector end = start + ( dir * dist );
  963. //Trace out
  964. UTIL_TraceLine( start, end, MASK_SHOT, this, COLLISION_GROUP_NONE, &trace );
  965. if ( trace.startsolid )
  966. {
  967. // ep1_citadel_04 has a phys_impact just behind another entity, so if we startsolid then
  968. // bump out just a little and retry the trace
  969. Vector startOffset = start + ( dir * 0.1 );
  970. UTIL_TraceLine( startOffset , end, MASK_SHOT, this, COLLISION_GROUP_NONE, &trace );
  971. }
  972. if( debug_physimpact.GetBool() )
  973. {
  974. NDebugOverlay::Cross3D( start, 24, 255, 255, 255, false, 30 );
  975. NDebugOverlay::Line( trace.startpos, trace.endpos, 0, 255, 0, false, 30 );
  976. }
  977. if ( trace.fraction != 1.0 )
  978. {
  979. // if inside the object, just go opposite the direction
  980. if ( trace.startsolid )
  981. {
  982. trace.plane.normal = -dir;
  983. }
  984. CBaseEntity *pEnt = trace.m_pEnt;
  985. IPhysicsObject *pPhysics = pEnt->VPhysicsGetObject();
  986. //If the entity is valid, hit it
  987. if ( ( pEnt != NULL ) && ( pPhysics != NULL ) )
  988. {
  989. CTakeDamageInfo info;
  990. info.SetAttacker( this);
  991. info.SetInflictor( this );
  992. info.SetDamage( 0 );
  993. info.SetDamageForce( vec3_origin );
  994. info.SetDamageType( DMG_GENERIC );
  995. pEnt->DispatchTraceAttack( info, dir, &trace );
  996. ApplyMultiDamage();
  997. //Damage falls off unless specified or the ray's length is infinite
  998. float damage = HasSpawnFlags( bitsPHYSIMPACT_NOFALLOFF | bitsPHYSIMPACT_INFINITE_LENGTH ) ?
  999. m_damage : (m_damage * (1.0f-trace.fraction));
  1000. if ( HasSpawnFlags( bitsPHYSIMPACT_IGNORE_MASS ) )
  1001. {
  1002. damage *= pPhysics->GetMass();
  1003. }
  1004. if( debug_physimpact.GetBool() )
  1005. {
  1006. NDebugOverlay::Line( trace.endpos, trace.endpos + trace.plane.normal * -128, 255, 0, 0, false, 30 );
  1007. }
  1008. // Legacy entities applied the force along the impact normal, which yielded unpredictable results.
  1009. if ( !HasSpawnFlags( bitsPHYSIMPACT_IGNORE_NORMAL ) )
  1010. {
  1011. dir = -trace.plane.normal;
  1012. }
  1013. pPhysics->ApplyForceOffset( damage * dir * phys_pushscale.GetFloat(), trace.endpos );
  1014. }
  1015. }
  1016. }
  1017. class CSimplePhysicsBrush : public CBaseEntity
  1018. {
  1019. DECLARE_CLASS( CSimplePhysicsBrush, CBaseEntity );
  1020. public:
  1021. void Spawn()
  1022. {
  1023. SetModel( STRING( GetModelName() ) );
  1024. SetMoveType( MOVETYPE_VPHYSICS );
  1025. SetSolid( SOLID_VPHYSICS );
  1026. m_takedamage = DAMAGE_EVENTS_ONLY;
  1027. }
  1028. };
  1029. LINK_ENTITY_TO_CLASS( simple_physics_brush, CSimplePhysicsBrush );
  1030. class CSimplePhysicsProp : public CBaseProp
  1031. {
  1032. DECLARE_CLASS( CSimplePhysicsProp, CBaseProp );
  1033. public:
  1034. void Spawn()
  1035. {
  1036. BaseClass::Spawn();
  1037. SetMoveType( MOVETYPE_VPHYSICS );
  1038. SetSolid( SOLID_VPHYSICS );
  1039. m_takedamage = DAMAGE_EVENTS_ONLY;
  1040. }
  1041. int ObjectCaps()
  1042. {
  1043. int caps = BaseClass::ObjectCaps() | FCAP_WCEDIT_POSITION;
  1044. if ( CBasePlayer::CanPickupObject( this, 35, 128 ) )
  1045. {
  1046. caps |= FCAP_IMPULSE_USE;
  1047. }
  1048. return caps;
  1049. }
  1050. void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  1051. {
  1052. CBasePlayer *pPlayer = ToBasePlayer( pActivator );
  1053. if ( pPlayer )
  1054. {
  1055. pPlayer->PickupObject( this );
  1056. }
  1057. }
  1058. };
  1059. LINK_ENTITY_TO_CLASS( simple_physics_prop, CSimplePhysicsProp );
  1060. // UNDONE: Is this worth it?, just recreate the object instead? (that happens when this returns false anyway)
  1061. // recreating works, but is more expensive and won't inherit properties (velocity, constraints, etc)
  1062. bool TransferPhysicsObject( CBaseEntity *pFrom, CBaseEntity *pTo, bool wakeUp )
  1063. {
  1064. IPhysicsObject *pVPhysics = pFrom->VPhysicsGetObject();
  1065. if ( !pVPhysics || pVPhysics->IsStatic() )
  1066. return false;
  1067. // clear out the pointer so it won't get deleted
  1068. pFrom->VPhysicsSwapObject( NULL );
  1069. // remove any AI behavior bound to it
  1070. pVPhysics->RemoveShadowController();
  1071. // transfer to the new owner
  1072. pTo->VPhysicsSetObject( pVPhysics );
  1073. pVPhysics->SetGameData( (void *)pTo );
  1074. pTo->VPhysicsUpdate( pVPhysics );
  1075. // may have been temporarily disabled by the old object
  1076. pVPhysics->EnableMotion( true );
  1077. pVPhysics->EnableGravity( true );
  1078. // Update for the new entity solid type
  1079. pVPhysics->RecheckCollisionFilter();
  1080. if ( wakeUp )
  1081. {
  1082. pVPhysics->Wake();
  1083. }
  1084. return true;
  1085. }
  1086. // UNDONE: Move/rename this function
  1087. static CBaseEntity *CreateSimplePhysicsObject( CBaseEntity *pEntity, bool createAsleep, bool createAsDebris )
  1088. {
  1089. CBaseEntity *pPhysEntity = NULL;
  1090. int modelindex = pEntity->GetModelIndex();
  1091. const model_t *model = modelinfo->GetModel( modelindex );
  1092. if ( model && modelinfo->GetModelType(model) == mod_brush )
  1093. {
  1094. pPhysEntity = CreateEntityByName( "simple_physics_brush" );
  1095. }
  1096. else
  1097. {
  1098. pPhysEntity = CreateEntityByName( "simple_physics_prop" );
  1099. }
  1100. pPhysEntity->KeyValue( "model", STRING(pEntity->GetModelName()) );
  1101. pPhysEntity->SetAbsOrigin( pEntity->GetAbsOrigin() );
  1102. pPhysEntity->SetAbsAngles( pEntity->GetAbsAngles() );
  1103. pPhysEntity->Spawn();
  1104. if ( !TransferPhysicsObject( pEntity, pPhysEntity, !createAsleep ) )
  1105. {
  1106. pPhysEntity->VPhysicsInitNormal( SOLID_VPHYSICS, 0, createAsleep );
  1107. if ( createAsDebris )
  1108. pPhysEntity->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  1109. }
  1110. return pPhysEntity;
  1111. }
  1112. #define SF_CONVERT_ASLEEP 0x0001
  1113. #define SF_CONVERT_AS_DEBRIS 0x0002
  1114. class CPhysConvert : public CLogicalEntity
  1115. {
  1116. DECLARE_CLASS( CPhysConvert, CLogicalEntity );
  1117. public:
  1118. CPhysConvert( void ) : m_flMassOverride( 0.0f ) {};
  1119. COutputEvent m_OnConvert;
  1120. // Input handlers
  1121. void InputConvertTarget( inputdata_t &inputdata );
  1122. DECLARE_DATADESC();
  1123. private:
  1124. string_t m_swapModel;
  1125. float m_flMassOverride;
  1126. };
  1127. LINK_ENTITY_TO_CLASS( phys_convert, CPhysConvert );
  1128. BEGIN_DATADESC( CPhysConvert )
  1129. DEFINE_KEYFIELD( m_swapModel, FIELD_STRING, "swapmodel" ),
  1130. DEFINE_KEYFIELD( m_flMassOverride, FIELD_FLOAT, "massoverride" ),
  1131. // Inputs
  1132. DEFINE_INPUTFUNC( FIELD_VOID, "ConvertTarget", InputConvertTarget ),
  1133. // Outputs
  1134. DEFINE_OUTPUT( m_OnConvert, "OnConvert"),
  1135. END_DATADESC()
  1136. //-----------------------------------------------------------------------------
  1137. // Purpose: Input handler that converts our target to a physics object.
  1138. //-----------------------------------------------------------------------------
  1139. void CPhysConvert::InputConvertTarget( inputdata_t &inputdata )
  1140. {
  1141. bool createAsleep = HasSpawnFlags(SF_CONVERT_ASLEEP);
  1142. bool createAsDebris = HasSpawnFlags(SF_CONVERT_AS_DEBRIS);
  1143. // Fire output
  1144. m_OnConvert.FireOutput( inputdata.pActivator, this );
  1145. CBaseEntity *entlist[512];
  1146. CBaseEntity *pSwap = gEntList.FindEntityByName( NULL, m_swapModel, NULL, inputdata.pActivator, inputdata.pCaller );
  1147. CBaseEntity *pEntity = NULL;
  1148. int count = 0;
  1149. while ( (pEntity = gEntList.FindEntityByName( pEntity, m_target, NULL, inputdata.pActivator, inputdata.pCaller )) != NULL )
  1150. {
  1151. entlist[count++] = pEntity;
  1152. if ( count >= ARRAYSIZE(entlist) )
  1153. break;
  1154. }
  1155. // if we're swapping to model out, don't loop over more than one object
  1156. // multiple objects with the same brush model will render, but the dynamic lights
  1157. // and decals will be shared between the two instances...
  1158. if ( pSwap && count > 0 )
  1159. {
  1160. count = 1;
  1161. }
  1162. for ( int i = 0; i < count; i++ )
  1163. {
  1164. pEntity = entlist[i];
  1165. // don't convert something that is already physics based
  1166. if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
  1167. {
  1168. Msg( "ERROR phys_convert %s ! Already MOVETYPE_VPHYSICS\n", STRING(pEntity->m_iClassname) );
  1169. continue;
  1170. }
  1171. UnlinkFromParent( pEntity );
  1172. if ( pSwap )
  1173. {
  1174. // we can't reuse this physics object, so kill it
  1175. pEntity->VPhysicsDestroyObject();
  1176. pEntity->SetModel( STRING(pSwap->GetModelName()) );
  1177. }
  1178. // created phys object, now move hierarchy over
  1179. CBaseEntity *pPhys = CreateSimplePhysicsObject( pEntity, createAsleep, createAsDebris );
  1180. if ( pPhys )
  1181. {
  1182. // Override the mass if specified
  1183. if ( m_flMassOverride > 0 )
  1184. {
  1185. IPhysicsObject *pPhysObj = pPhys->VPhysicsGetObject();
  1186. if ( pPhysObj )
  1187. {
  1188. pPhysObj->SetMass( m_flMassOverride );
  1189. }
  1190. }
  1191. pPhys->SetName( pEntity->GetEntityName() );
  1192. UTIL_TransferPoseParameters( pEntity, pPhys );
  1193. TransferChildren( pEntity, pPhys );
  1194. pEntity->AddSolidFlags( FSOLID_NOT_SOLID );
  1195. pEntity->AddEffects( EF_NODRAW );
  1196. UTIL_Remove( pEntity );
  1197. }
  1198. }
  1199. }
  1200. //============================================================================================================
  1201. // PHYS MAGNET
  1202. //============================================================================================================
  1203. #define SF_MAGNET_ASLEEP 0x0001
  1204. #define SF_MAGNET_MOTIONDISABLED 0x0002
  1205. #define SF_MAGNET_SUCK 0x0004
  1206. #define SF_MAGNET_ALLOWROTATION 0x0008
  1207. #define SF_MAGNET_COAST_HACK 0x0010
  1208. LINK_ENTITY_TO_CLASS( phys_magnet, CPhysMagnet );
  1209. // BUGBUG: This won't work! Right now you can't save physics pointers inside an embedded type!
  1210. BEGIN_SIMPLE_DATADESC( magnetted_objects_t )
  1211. DEFINE_PHYSPTR( pConstraint ),
  1212. DEFINE_FIELD( hEntity, FIELD_EHANDLE ),
  1213. END_DATADESC()
  1214. BEGIN_DATADESC( CPhysMagnet )
  1215. // Outputs
  1216. DEFINE_OUTPUT( m_OnMagnetAttach, "OnAttach" ),
  1217. DEFINE_OUTPUT( m_OnMagnetDetach, "OnDetach" ),
  1218. // Keys
  1219. DEFINE_KEYFIELD( m_massScale, FIELD_FLOAT, "massScale" ),
  1220. DEFINE_KEYFIELD( m_iszOverrideScript, FIELD_STRING, "overridescript" ),
  1221. DEFINE_KEYFIELD( m_iMaxObjectsAttached, FIELD_INTEGER, "maxobjects" ),
  1222. DEFINE_KEYFIELD( m_forceLimit, FIELD_FLOAT, "forcelimit" ),
  1223. DEFINE_KEYFIELD( m_torqueLimit, FIELD_FLOAT, "torquelimit" ),
  1224. DEFINE_UTLVECTOR( m_MagnettedEntities, FIELD_EMBEDDED ),
  1225. DEFINE_PHYSPTR( m_pConstraintGroup ),
  1226. DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ),
  1227. DEFINE_FIELD( m_bHasHitSomething, FIELD_BOOLEAN ),
  1228. DEFINE_FIELD( m_flTotalMass, FIELD_FLOAT ),
  1229. DEFINE_FIELD( m_flRadius, FIELD_FLOAT ),
  1230. DEFINE_FIELD( m_flNextSuckTime, FIELD_FLOAT ),
  1231. // Inputs
  1232. DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
  1233. DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
  1234. DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
  1235. END_DATADESC()
  1236. //-----------------------------------------------------------------------------
  1237. // Purpose: SendProxy that converts the magnet's attached object UtlVector to entindexes
  1238. //-----------------------------------------------------------------------------
  1239. void SendProxy_MagnetAttachedObjectList( const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
  1240. {
  1241. CPhysMagnet *pMagnet = (CPhysMagnet*)pData;
  1242. // If this assertion fails, then SendProxyArrayLength_MagnetAttachedArray must have failed.
  1243. Assert( iElement < pMagnet->GetNumAttachedObjects() );
  1244. pOut->m_Int = pMagnet->GetAttachedObject(iElement)->entindex();
  1245. }
  1246. int SendProxyArrayLength_MagnetAttachedArray( const void *pStruct, int objectID )
  1247. {
  1248. CPhysMagnet *pMagnet = (CPhysMagnet*)pStruct;
  1249. return pMagnet->GetNumAttachedObjects();
  1250. }
  1251. IMPLEMENT_SERVERCLASS_ST( CPhysMagnet, DT_PhysMagnet )
  1252. // ROBIN: Disabled because we don't need it anymore
  1253. /*
  1254. SendPropArray2(
  1255. SendProxyArrayLength_MagnetAttachedArray,
  1256. SendPropInt("magnetattached_array_element", 0, 4, 10, SPROP_UNSIGNED, SendProxy_MagnetAttachedObjectList),
  1257. 128,
  1258. 0,
  1259. "magnetattached_array"
  1260. )
  1261. */
  1262. END_SEND_TABLE()
  1263. //-----------------------------------------------------------------------------
  1264. // Purpose:
  1265. //-----------------------------------------------------------------------------
  1266. CPhysMagnet::CPhysMagnet( void )
  1267. {
  1268. m_forceLimit = 0;
  1269. m_torqueLimit = 0;
  1270. }
  1271. //-----------------------------------------------------------------------------
  1272. // Purpose:
  1273. //-----------------------------------------------------------------------------
  1274. CPhysMagnet::~CPhysMagnet( void )
  1275. {
  1276. DetachAll();
  1277. }
  1278. //-----------------------------------------------------------------------------
  1279. // Purpose:
  1280. //-----------------------------------------------------------------------------
  1281. void CPhysMagnet::Spawn( void )
  1282. {
  1283. Precache();
  1284. SetMoveType( MOVETYPE_NONE );
  1285. SetSolid( SOLID_VPHYSICS );
  1286. SetModel( STRING( GetModelName() ) );
  1287. m_takedamage = DAMAGE_EVENTS_ONLY;
  1288. solid_t tmpSolid;
  1289. PhysModelParseSolid( tmpSolid, this, GetModelIndex() );
  1290. if ( m_massScale > 0 )
  1291. {
  1292. tmpSolid.params.mass *= m_massScale;
  1293. }
  1294. PhysSolidOverride( tmpSolid, m_iszOverrideScript );
  1295. VPhysicsInitNormal( GetSolid(), GetSolidFlags(), true, &tmpSolid );
  1296. // Wake it up if not asleep
  1297. if ( !HasSpawnFlags(SF_MAGNET_ASLEEP) )
  1298. {
  1299. VPhysicsGetObject()->Wake();
  1300. }
  1301. if ( HasSpawnFlags(SF_MAGNET_MOTIONDISABLED) )
  1302. {
  1303. VPhysicsGetObject()->EnableMotion( false );
  1304. }
  1305. m_bActive = true;
  1306. m_pConstraintGroup = NULL;
  1307. m_flTotalMass = 0;
  1308. m_flNextSuckTime = 0;
  1309. BaseClass::Spawn();
  1310. }
  1311. //-----------------------------------------------------------------------------
  1312. // Purpose:
  1313. //-----------------------------------------------------------------------------
  1314. void CPhysMagnet::Precache( void )
  1315. {
  1316. PrecacheModel( STRING( GetModelName() ) );
  1317. BaseClass::Precache();
  1318. }
  1319. //-----------------------------------------------------------------------------
  1320. // Purpose:
  1321. //-----------------------------------------------------------------------------
  1322. void CPhysMagnet::Touch( CBaseEntity *pOther )
  1323. {
  1324. }
  1325. //-----------------------------------------------------------------------------
  1326. // Purpose:
  1327. //-----------------------------------------------------------------------------
  1328. void CPhysMagnet::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  1329. {
  1330. int otherIndex = !index;
  1331. CBaseEntity *pOther = pEvent->pEntities[otherIndex];
  1332. // Ignore triggers
  1333. if ( pOther->IsSolidFlagSet( FSOLID_NOT_SOLID ) )
  1334. return;
  1335. m_bHasHitSomething = true;
  1336. DoMagnetSuck( pEvent->pEntities[!index] );
  1337. // Don't pickup if we're not active
  1338. if ( !m_bActive )
  1339. return;
  1340. // Hit our maximum?
  1341. if ( m_iMaxObjectsAttached && m_iMaxObjectsAttached <= GetNumAttachedObjects() )
  1342. return;
  1343. // This is a hack to solve players (Erik) stacking stuff on their jeeps in coast_01
  1344. // and being screwed when the crane can't pick them up. We need to get rid of the object.
  1345. if ( HasSpawnFlags( SF_MAGNET_COAST_HACK ) )
  1346. {
  1347. // If the other isn't the jeep, we need to get rid of it
  1348. if ( !FClassnameIs( pOther, "prop_vehicle_jeep" ) )
  1349. {
  1350. // If it takes damage, destroy it
  1351. if ( pOther->m_takedamage != DAMAGE_NO && pOther->m_takedamage != DAMAGE_EVENTS_ONLY )
  1352. {
  1353. CTakeDamageInfo info( this, this, pOther->GetHealth(), DMG_GENERIC | DMG_PREVENT_PHYSICS_FORCE );
  1354. pOther->TakeDamage( info );
  1355. }
  1356. else if ( pEvent->pObjects[ otherIndex ]->IsMoveable() )
  1357. {
  1358. // Otherwise, we're screwed, so just remove it
  1359. UTIL_Remove( pOther );
  1360. }
  1361. else
  1362. {
  1363. Warning( "CPhysMagnet %s:%d blocking magnet\n",
  1364. pOther->GetClassname(), pOther->entindex() );
  1365. }
  1366. return;
  1367. }
  1368. }
  1369. // Make sure it's made of metal
  1370. const surfacedata_t *phit = physprops->GetSurfaceData( pEvent->surfaceProps[otherIndex] );
  1371. char cTexType = phit->game.material;
  1372. if ( cTexType != CHAR_TEX_METAL && cTexType != CHAR_TEX_COMPUTER )
  1373. {
  1374. // If we don't have a model, we're done. The texture we hit wasn't metal.
  1375. if ( !pOther->GetBaseAnimating() )
  1376. return;
  1377. // If we have a model that wants to be metal, even though we hit a non-metal texture, we'll stick to it
  1378. if ( Q_strncmp( Studio_GetDefaultSurfaceProps( pOther->GetBaseAnimating()->GetModelPtr() ), "metal", 5 ) )
  1379. return;
  1380. }
  1381. IPhysicsObject *pPhysics = pOther->VPhysicsGetObject();
  1382. if ( pPhysics && pOther->GetMoveType() == MOVETYPE_VPHYSICS && pPhysics->IsMoveable() )
  1383. {
  1384. // Make sure we haven't already got this sucker on the magnet
  1385. int iCount = m_MagnettedEntities.Count();
  1386. for ( int i = 0; i < iCount; i++ )
  1387. {
  1388. if ( m_MagnettedEntities[i].hEntity == pOther )
  1389. return;
  1390. }
  1391. // We want to cast a long way to ensure our shadow shows up
  1392. pOther->SetShadowCastDistance( 2048 );
  1393. // Create a constraint between the magnet and this sucker
  1394. IPhysicsObject *pMagnetPhysObject = VPhysicsGetObject();
  1395. Assert( pMagnetPhysObject );
  1396. magnetted_objects_t newEntityOnMagnet;
  1397. newEntityOnMagnet.hEntity = pOther;
  1398. // Use the right constraint
  1399. if ( HasSpawnFlags( SF_MAGNET_ALLOWROTATION ) )
  1400. {
  1401. constraint_ballsocketparams_t ballsocket;
  1402. ballsocket.Defaults();
  1403. ballsocket.constraint.Defaults();
  1404. ballsocket.constraint.forceLimit = lbs2kg(m_forceLimit);
  1405. ballsocket.constraint.torqueLimit = lbs2kg(m_torqueLimit);
  1406. Vector vecCollisionPoint;
  1407. pEvent->pInternalData->GetContactPoint( vecCollisionPoint );
  1408. pMagnetPhysObject->WorldToLocal( &ballsocket.constraintPosition[0], vecCollisionPoint );
  1409. pPhysics->WorldToLocal( &ballsocket.constraintPosition[1], vecCollisionPoint );
  1410. //newEntityOnMagnet.pConstraint = physenv->CreateBallsocketConstraint( pMagnetPhysObject, pPhysics, m_pConstraintGroup, ballsocket );
  1411. newEntityOnMagnet.pConstraint = physenv->CreateBallsocketConstraint( pMagnetPhysObject, pPhysics, NULL, ballsocket );
  1412. }
  1413. else
  1414. {
  1415. constraint_fixedparams_t fixed;
  1416. fixed.Defaults();
  1417. fixed.InitWithCurrentObjectState( pMagnetPhysObject, pPhysics );
  1418. fixed.constraint.Defaults();
  1419. fixed.constraint.forceLimit = lbs2kg(m_forceLimit);
  1420. fixed.constraint.torqueLimit = lbs2kg(m_torqueLimit);
  1421. // FIXME: Use the magnet's constraint group.
  1422. //newEntityOnMagnet.pConstraint = physenv->CreateFixedConstraint( pMagnetPhysObject, pPhysics, m_pConstraintGroup, fixed );
  1423. newEntityOnMagnet.pConstraint = physenv->CreateFixedConstraint( pMagnetPhysObject, pPhysics, NULL, fixed );
  1424. }
  1425. newEntityOnMagnet.pConstraint->SetGameData( (void *) this );
  1426. m_MagnettedEntities.AddToTail( newEntityOnMagnet );
  1427. m_flTotalMass += pPhysics->GetMass();
  1428. }
  1429. DoMagnetSuck( pOther );
  1430. m_OnMagnetAttach.FireOutput( this, this );
  1431. BaseClass::VPhysicsCollision( index, pEvent );
  1432. }
  1433. //-----------------------------------------------------------------------------
  1434. // Purpose:
  1435. //-----------------------------------------------------------------------------
  1436. void CPhysMagnet::DoMagnetSuck( CBaseEntity *pOther )
  1437. {
  1438. if ( !HasSpawnFlags( SF_MAGNET_SUCK ) )
  1439. return;
  1440. if ( !m_bActive )
  1441. return;
  1442. // Don't repeatedly suck
  1443. if ( m_flNextSuckTime > gpGlobals->curtime )
  1444. return;
  1445. // Look for physics objects underneath the magnet and suck them onto it
  1446. Vector vecCheckPos, vecSuckPoint;
  1447. VectorTransform( Vector(0,0,-96), EntityToWorldTransform(), vecCheckPos );
  1448. VectorTransform( Vector(0,0,-64), EntityToWorldTransform(), vecSuckPoint );
  1449. CBaseEntity *pEntities[20];
  1450. int iNumEntities = UTIL_EntitiesInSphere( pEntities, 20, vecCheckPos, 80.0, 0 );
  1451. for ( int i = 0; i < iNumEntities; i++ )
  1452. {
  1453. CBaseEntity *pEntity = pEntities[i];
  1454. if ( !pEntity || pEntity == pOther )
  1455. continue;
  1456. IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
  1457. if ( pPhys && pEntity->GetMoveType() == MOVETYPE_VPHYSICS && pPhys->GetMass() < 5000 )
  1458. {
  1459. // Do we have line of sight to it?
  1460. trace_t tr;
  1461. UTIL_TraceLine( GetAbsOrigin(), pEntity->GetAbsOrigin(), MASK_SHOT, this, 0, &tr );
  1462. if ( tr.fraction == 1.0 || tr.m_pEnt == pEntity )
  1463. {
  1464. // Pull it towards the magnet
  1465. Vector vecVelocity = (vecSuckPoint - pEntity->GetAbsOrigin());
  1466. VectorNormalize(vecVelocity);
  1467. vecVelocity *= 5 * pPhys->GetMass();
  1468. pPhys->AddVelocity( &vecVelocity, NULL );
  1469. }
  1470. }
  1471. }
  1472. m_flNextSuckTime = gpGlobals->curtime + 2.0;
  1473. }
  1474. //-----------------------------------------------------------------------------
  1475. // Purpose:
  1476. //-----------------------------------------------------------------------------
  1477. void CPhysMagnet::SetConstraintGroup( IPhysicsConstraintGroup *pGroup )
  1478. {
  1479. m_pConstraintGroup = pGroup;
  1480. }
  1481. //-----------------------------------------------------------------------------
  1482. // Purpose: Make the magnet active
  1483. //-----------------------------------------------------------------------------
  1484. void CPhysMagnet::InputTurnOn( inputdata_t &inputdata )
  1485. {
  1486. m_bActive = true;
  1487. }
  1488. //-----------------------------------------------------------------------------
  1489. // Purpose: Make the magnet inactive. Drop everything it's got hooked on.
  1490. //-----------------------------------------------------------------------------
  1491. void CPhysMagnet::InputTurnOff( inputdata_t &inputdata )
  1492. {
  1493. m_bActive = false;
  1494. DetachAll();
  1495. }
  1496. //-----------------------------------------------------------------------------
  1497. // Purpose: Toggle the magnet's active state
  1498. //-----------------------------------------------------------------------------
  1499. void CPhysMagnet::InputToggle( inputdata_t &inputdata )
  1500. {
  1501. if ( m_bActive )
  1502. {
  1503. InputTurnOff( inputdata );
  1504. }
  1505. else
  1506. {
  1507. InputTurnOn( inputdata );
  1508. }
  1509. }
  1510. //-----------------------------------------------------------------------------
  1511. // Purpose: One of our magnet constraints broke
  1512. //-----------------------------------------------------------------------------
  1513. void CPhysMagnet::ConstraintBroken( IPhysicsConstraint *pConstraint )
  1514. {
  1515. // Find the entity that was constrained and release it
  1516. int iCount = m_MagnettedEntities.Count();
  1517. for ( int i = 0; i < iCount; i++ )
  1518. {
  1519. if ( m_MagnettedEntities[i].hEntity.Get() != NULL && m_MagnettedEntities[i].pConstraint == pConstraint )
  1520. {
  1521. IPhysicsObject *pPhysObject = m_MagnettedEntities[i].hEntity->VPhysicsGetObject();
  1522. if( pPhysObject != NULL )
  1523. {
  1524. m_flTotalMass -= pPhysObject->GetMass();
  1525. }
  1526. m_MagnettedEntities.Remove(i);
  1527. break;
  1528. }
  1529. }
  1530. m_OnMagnetDetach.FireOutput( this, this );
  1531. physenv->DestroyConstraint( pConstraint );
  1532. }
  1533. //-----------------------------------------------------------------------------
  1534. // Purpose:
  1535. //-----------------------------------------------------------------------------
  1536. void CPhysMagnet::DetachAll( void )
  1537. {
  1538. // Make sure we haven't already got this sucker on the magnet
  1539. int iCount = m_MagnettedEntities.Count();
  1540. for ( int i = 0; i < iCount; i++ )
  1541. {
  1542. // Delay a couple seconds to reset to the default shadow cast behavior
  1543. if ( m_MagnettedEntities[i].hEntity )
  1544. {
  1545. m_MagnettedEntities[i].hEntity->SetShadowCastDistance( 0, 2.0f );
  1546. }
  1547. physenv->DestroyConstraint( m_MagnettedEntities[i].pConstraint );
  1548. }
  1549. m_MagnettedEntities.Purge();
  1550. m_flTotalMass = 0;
  1551. }
  1552. //-----------------------------------------------------------------------------
  1553. // Purpose:
  1554. //-----------------------------------------------------------------------------
  1555. int CPhysMagnet::GetNumAttachedObjects( void )
  1556. {
  1557. return m_MagnettedEntities.Count();
  1558. }
  1559. //-----------------------------------------------------------------------------
  1560. // Purpose:
  1561. //-----------------------------------------------------------------------------
  1562. float CPhysMagnet::GetTotalMassAttachedObjects( void )
  1563. {
  1564. return m_flTotalMass;
  1565. }
  1566. //-----------------------------------------------------------------------------
  1567. // Purpose:
  1568. //-----------------------------------------------------------------------------
  1569. CBaseEntity *CPhysMagnet::GetAttachedObject( int iIndex )
  1570. {
  1571. Assert( iIndex < GetNumAttachedObjects() );
  1572. return m_MagnettedEntities[iIndex].hEntity;
  1573. }
  1574. class CInfoMassCenter : public CPointEntity
  1575. {
  1576. DECLARE_CLASS( CInfoMassCenter, CPointEntity );
  1577. public:
  1578. void Spawn( void )
  1579. {
  1580. if ( m_target != NULL_STRING )
  1581. {
  1582. masscenteroverride_t params;
  1583. params.SnapToPoint( m_target, GetAbsOrigin() );
  1584. PhysSetMassCenterOverride( params );
  1585. UTIL_Remove( this );
  1586. }
  1587. }
  1588. };
  1589. LINK_ENTITY_TO_CLASS( info_mass_center, CInfoMassCenter );
  1590. // =============================================================
  1591. // point_push
  1592. // =============================================================
  1593. class CPointPush : public CPointEntity
  1594. {
  1595. public:
  1596. DECLARE_CLASS( CPointPush, CPointEntity );
  1597. virtual void Activate( void );
  1598. void PushThink( void );
  1599. void InputEnable( inputdata_t &inputdata );
  1600. void InputDisable( inputdata_t &inputdata );
  1601. DECLARE_DATADESC();
  1602. private:
  1603. inline void PushEntity( CBaseEntity *pTarget );
  1604. bool m_bEnabled;
  1605. float m_flMagnitude;
  1606. float m_flRadius;
  1607. float m_flInnerRadius; // Inner radius where the push eminates from (on a sphere)
  1608. };
  1609. LINK_ENTITY_TO_CLASS( point_push, CPointPush );
  1610. BEGIN_DATADESC( CPointPush )
  1611. DEFINE_THINKFUNC( PushThink ),
  1612. DEFINE_KEYFIELD( m_bEnabled, FIELD_BOOLEAN, "enabled" ),
  1613. DEFINE_KEYFIELD( m_flMagnitude, FIELD_FLOAT, "magnitude" ),
  1614. DEFINE_KEYFIELD( m_flRadius, FIELD_FLOAT, "radius" ),
  1615. DEFINE_KEYFIELD( m_flInnerRadius,FIELD_FLOAT, "inner_radius" ),
  1616. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ),
  1617. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ),
  1618. END_DATADESC();
  1619. // Spawnflags
  1620. #define SF_PUSH_TEST_LOS 0x0001
  1621. #define SF_PUSH_DIRECTIONAL 0x0002
  1622. #define SF_PUSH_NO_FALLOFF 0x0004
  1623. #define SF_PUSH_PLAYER 0x0008
  1624. #define SF_PUSH_PHYSICS 0x0010
  1625. //-----------------------------------------------------------------------------
  1626. // Purpose:
  1627. //-----------------------------------------------------------------------------
  1628. void CPointPush::Activate( void )
  1629. {
  1630. if ( m_bEnabled )
  1631. {
  1632. SetThink( &CPointPush::PushThink );
  1633. SetNextThink( gpGlobals->curtime + 0.05f );
  1634. }
  1635. BaseClass::Activate();
  1636. }
  1637. //-----------------------------------------------------------------------------
  1638. // Purpose:
  1639. // Input : *pTarget -
  1640. //-----------------------------------------------------------------------------
  1641. void CPointPush::PushEntity( CBaseEntity *pTarget )
  1642. {
  1643. Vector vecPushDir;
  1644. if ( HasSpawnFlags( SF_PUSH_DIRECTIONAL ) )
  1645. {
  1646. GetVectors( &vecPushDir, NULL, NULL );
  1647. }
  1648. else
  1649. {
  1650. vecPushDir = ( pTarget->BodyTarget( GetAbsOrigin(), false ) - GetAbsOrigin() );
  1651. }
  1652. float dist = VectorNormalize( vecPushDir );
  1653. float flFalloff = ( HasSpawnFlags( SF_PUSH_NO_FALLOFF ) ) ? 1.0f : RemapValClamped( dist, m_flRadius, m_flRadius*0.25f, 0.0f, 1.0f );
  1654. switch( pTarget->GetMoveType() )
  1655. {
  1656. case MOVETYPE_NONE:
  1657. case MOVETYPE_PUSH:
  1658. case MOVETYPE_NOCLIP:
  1659. break;
  1660. case MOVETYPE_VPHYSICS:
  1661. {
  1662. IPhysicsObject *pPhys = pTarget->VPhysicsGetObject();
  1663. if ( pPhys )
  1664. {
  1665. // UNDONE: Assume the velocity is for a 100kg object, scale with mass
  1666. pPhys->ApplyForceCenter( m_flMagnitude * flFalloff * 100.0f * vecPushDir * pPhys->GetMass() * gpGlobals->frametime );
  1667. return;
  1668. }
  1669. }
  1670. break;
  1671. case MOVETYPE_STEP:
  1672. {
  1673. // NPCs cannot be lifted up properly, they need to move in 2D
  1674. vecPushDir.z = 0.0f;
  1675. // NOTE: Falls through!
  1676. }
  1677. default:
  1678. {
  1679. Vector vecPush = (m_flMagnitude * vecPushDir * flFalloff);
  1680. if ( pTarget->GetFlags() & FL_BASEVELOCITY )
  1681. {
  1682. vecPush = vecPush + pTarget->GetBaseVelocity();
  1683. }
  1684. if ( vecPush.z > 0 && (pTarget->GetFlags() & FL_ONGROUND) )
  1685. {
  1686. pTarget->SetGroundEntity( NULL );
  1687. Vector origin = pTarget->GetAbsOrigin();
  1688. origin.z += 1.0f;
  1689. pTarget->SetAbsOrigin( origin );
  1690. }
  1691. pTarget->SetBaseVelocity( vecPush );
  1692. pTarget->AddFlag( FL_BASEVELOCITY );
  1693. }
  1694. break;
  1695. }
  1696. }
  1697. //-----------------------------------------------------------------------------
  1698. // Purpose:
  1699. //-----------------------------------------------------------------------------
  1700. void CPointPush::PushThink( void )
  1701. {
  1702. // Get a collection of entities in a radius around us
  1703. CBaseEntity *pEnts[256];
  1704. int numEnts = UTIL_EntitiesInSphere( pEnts, 256, GetAbsOrigin(), m_flRadius, 0 );
  1705. for ( int i = 0; i < numEnts; i++ )
  1706. {
  1707. // Must be solid
  1708. if ( pEnts[i]->IsSolid() == false )
  1709. continue;
  1710. // Cannot be parented (only push parents)
  1711. if ( pEnts[i]->GetMoveParent() != NULL )
  1712. continue;
  1713. // Must be moveable
  1714. if ( pEnts[i]->GetMoveType() != MOVETYPE_VPHYSICS &&
  1715. pEnts[i]->GetMoveType() != MOVETYPE_WALK &&
  1716. pEnts[i]->GetMoveType() != MOVETYPE_STEP )
  1717. continue;
  1718. // If we don't want to push players, don't
  1719. if ( pEnts[i]->IsPlayer() && HasSpawnFlags( SF_PUSH_PLAYER ) == false )
  1720. continue;
  1721. // If we don't want to push physics, don't
  1722. if ( pEnts[i]->GetMoveType() == MOVETYPE_VPHYSICS && HasSpawnFlags( SF_PUSH_PHYSICS ) == false )
  1723. continue;
  1724. // Test for LOS if asked to
  1725. if ( HasSpawnFlags( SF_PUSH_TEST_LOS ) )
  1726. {
  1727. Vector vecStartPos = GetAbsOrigin();
  1728. Vector vecEndPos = pEnts[i]->BodyTarget( vecStartPos, false );
  1729. if ( m_flInnerRadius != 0.0f )
  1730. {
  1731. // Find a point on our inner radius sphere to begin from
  1732. Vector vecDirToTarget = ( vecEndPos - vecStartPos );
  1733. VectorNormalize( vecDirToTarget );
  1734. vecStartPos = GetAbsOrigin() + ( vecDirToTarget * m_flInnerRadius );
  1735. }
  1736. trace_t tr;
  1737. UTIL_TraceLine( vecStartPos,
  1738. pEnts[i]->BodyTarget( vecStartPos, false ),
  1739. MASK_SOLID_BRUSHONLY,
  1740. this,
  1741. COLLISION_GROUP_NONE,
  1742. &tr );
  1743. // Shielded
  1744. if ( tr.fraction < 1.0f && tr.m_pEnt != pEnts[i] )
  1745. continue;
  1746. }
  1747. // Push it along
  1748. PushEntity( pEnts[i] );
  1749. }
  1750. // Set us up for the next think
  1751. SetNextThink( gpGlobals->curtime + 0.05f );
  1752. }
  1753. //-----------------------------------------------------------------------------
  1754. // Purpose:
  1755. //-----------------------------------------------------------------------------
  1756. void CPointPush::InputEnable( inputdata_t &inputdata )
  1757. {
  1758. m_bEnabled = true;
  1759. SetThink( &CPointPush::PushThink );
  1760. SetNextThink( gpGlobals->curtime + 0.05f );
  1761. }
  1762. //-----------------------------------------------------------------------------
  1763. // Purpose:
  1764. //-----------------------------------------------------------------------------
  1765. void CPointPush::InputDisable( inputdata_t &inputdata )
  1766. {
  1767. m_bEnabled = false;
  1768. SetThink( NULL );
  1769. SetNextThink( gpGlobals->curtime );
  1770. }