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.

1523 lines
39 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //=============================================================================//
  7. #include "cbase.h"
  8. #include "beam_shared.h"
  9. #include "player.h"
  10. #include "gamerules.h"
  11. #include "basecombatweapon.h"
  12. #include "baseviewmodel.h"
  13. #include "vphysics/constraints.h"
  14. #include "physics.h"
  15. #include "in_buttons.h"
  16. #include "IEffects.h"
  17. #include "engine/IEngineSound.h"
  18. #include "ndebugoverlay.h"
  19. #include "physics_saverestore.h"
  20. #include "player_pickup.h"
  21. #include "SoundEmitterSystem/isoundemittersystembase.h"
  22. // memdbgon must be the last include file in a .cpp file!!!
  23. #include "tier0/memdbgon.h"
  24. ConVar phys_gunmass("phys_gunmass", "200");
  25. ConVar phys_gunvel("phys_gunvel", "400");
  26. ConVar phys_gunforce("phys_gunforce", "5e5" );
  27. ConVar phys_guntorque("phys_guntorque", "100" );
  28. ConVar phys_gunglueradius("phys_gunglueradius", "128" );
  29. static int g_physgunBeam;
  30. #define PHYSGUN_BEAM_SPRITE "sprites/physbeam.vmt"
  31. #define MAX_PELLETS 16
  32. class CWeaponGravityGun;
  33. class CGravityPellet : public CBaseAnimating
  34. {
  35. DECLARE_CLASS( CGravityPellet, CBaseAnimating );
  36. public:
  37. DECLARE_DATADESC();
  38. ~CGravityPellet();
  39. void Precache()
  40. {
  41. SetModelName( MAKE_STRING( "models/weapons/glueblob.mdl" ) );
  42. PrecacheModel( STRING( GetModelName() ) );
  43. BaseClass::Precache();
  44. }
  45. void Spawn()
  46. {
  47. Precache();
  48. SetModel( STRING( GetModelName() ) );
  49. SetSolid( SOLID_NONE );
  50. SetMoveType( MOVETYPE_NONE );
  51. AddEffects( EF_NOSHADOW );
  52. SetRenderColor( 255, 0, 0 );
  53. m_isInert = false;
  54. }
  55. bool IsInert()
  56. {
  57. return m_isInert;
  58. }
  59. bool MakeConstraint( CBaseEntity *pObject )
  60. {
  61. IPhysicsObject *pReference = g_PhysWorldObject;
  62. if ( GetMoveParent() )
  63. {
  64. pReference = GetMoveParent()->VPhysicsGetObject();
  65. }
  66. IPhysicsObject *pAttached = pObject->VPhysicsGetObject();
  67. if ( !pReference || !pAttached )
  68. {
  69. return false;
  70. }
  71. constraint_fixedparams_t fixed;
  72. fixed.Defaults();
  73. fixed.InitWithCurrentObjectState( pReference, pAttached );
  74. m_pConstraint = physenv->CreateFixedConstraint( pReference, pAttached, NULL, fixed );
  75. m_pConstraint->SetGameData( (void *)this );
  76. MakeInert();
  77. return true;
  78. }
  79. void MakeInert()
  80. {
  81. SetRenderColor( 64, 64, 128 );
  82. m_isInert = true;
  83. }
  84. void InputOnBreak( inputdata_t &inputdata )
  85. {
  86. UTIL_Remove(this);
  87. }
  88. IPhysicsConstraint *m_pConstraint;
  89. bool m_isInert;
  90. };
  91. LINK_ENTITY_TO_CLASS(gravity_pellet, CGravityPellet);
  92. PRECACHE_REGISTER(gravity_pellet);
  93. BEGIN_DATADESC( CGravityPellet )
  94. DEFINE_PHYSPTR( m_pConstraint ),
  95. DEFINE_FIELD( m_isInert, FIELD_BOOLEAN ),
  96. // physics system will fire this input if the constraint breaks due to physics
  97. DEFINE_INPUTFUNC( FIELD_VOID, "ConstraintBroken", InputOnBreak ),
  98. END_DATADESC()
  99. CGravityPellet::~CGravityPellet()
  100. {
  101. if ( m_pConstraint )
  102. {
  103. physenv->DestroyConstraint( m_pConstraint );
  104. }
  105. }
  106. class CGravControllerPoint : public IMotionEvent
  107. {
  108. DECLARE_SIMPLE_DATADESC();
  109. public:
  110. CGravControllerPoint( void );
  111. ~CGravControllerPoint( void );
  112. void AttachEntity( CBaseEntity *pEntity, IPhysicsObject *pPhys, const Vector &position );
  113. void DetachEntity( void );
  114. void SetMaxVelocity( float maxVel )
  115. {
  116. m_maxVel = maxVel;
  117. }
  118. void SetTargetPosition( const Vector &target )
  119. {
  120. m_targetPosition = target;
  121. if ( m_attachedEntity == NULL )
  122. {
  123. m_worldPosition = target;
  124. }
  125. m_timeToArrive = gpGlobals->frametime;
  126. }
  127. void SetAutoAlign( const Vector &localDir, const Vector &localPos, const Vector &worldAlignDir, const Vector &worldAlignPos )
  128. {
  129. m_align = true;
  130. m_localAlignNormal = -localDir;
  131. m_localAlignPosition = localPos;
  132. m_targetAlignNormal = worldAlignDir;
  133. m_targetAlignPosition = worldAlignPos;
  134. }
  135. void ClearAutoAlign()
  136. {
  137. m_align = false;
  138. }
  139. IMotionEvent::simresult_e Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular );
  140. Vector m_localPosition;
  141. Vector m_targetPosition;
  142. Vector m_worldPosition;
  143. Vector m_localAlignNormal;
  144. Vector m_localAlignPosition;
  145. Vector m_targetAlignNormal;
  146. Vector m_targetAlignPosition;
  147. bool m_align;
  148. float m_saveDamping;
  149. float m_maxVel;
  150. float m_maxAcceleration;
  151. Vector m_maxAngularAcceleration;
  152. EHANDLE m_attachedEntity;
  153. QAngle m_targetRotation;
  154. float m_timeToArrive;
  155. IPhysicsMotionController *m_controller;
  156. };
  157. BEGIN_SIMPLE_DATADESC( CGravControllerPoint )
  158. DEFINE_FIELD( m_localPosition, FIELD_VECTOR ),
  159. DEFINE_FIELD( m_targetPosition, FIELD_POSITION_VECTOR ),
  160. DEFINE_FIELD( m_worldPosition, FIELD_POSITION_VECTOR ),
  161. DEFINE_FIELD( m_localAlignNormal, FIELD_VECTOR ),
  162. DEFINE_FIELD( m_localAlignPosition, FIELD_VECTOR ),
  163. DEFINE_FIELD( m_targetAlignNormal, FIELD_VECTOR ),
  164. DEFINE_FIELD( m_targetAlignPosition, FIELD_POSITION_VECTOR ),
  165. DEFINE_FIELD( m_align, FIELD_BOOLEAN ),
  166. DEFINE_FIELD( m_saveDamping, FIELD_FLOAT ),
  167. DEFINE_FIELD( m_maxVel, FIELD_FLOAT ),
  168. DEFINE_FIELD( m_maxAcceleration, FIELD_FLOAT ),
  169. DEFINE_FIELD( m_maxAngularAcceleration, FIELD_VECTOR ),
  170. DEFINE_FIELD( m_attachedEntity, FIELD_EHANDLE ),
  171. DEFINE_FIELD( m_targetRotation, FIELD_VECTOR ),
  172. DEFINE_FIELD( m_timeToArrive, FIELD_FLOAT ),
  173. // Physptrs can't be saved in embedded classes... this is to silence classcheck
  174. // DEFINE_PHYSPTR( m_controller ),
  175. END_DATADESC()
  176. CGravControllerPoint::CGravControllerPoint( void )
  177. {
  178. m_attachedEntity = NULL;
  179. }
  180. CGravControllerPoint::~CGravControllerPoint( void )
  181. {
  182. DetachEntity();
  183. }
  184. void CGravControllerPoint::AttachEntity( CBaseEntity *pEntity, IPhysicsObject *pPhys, const Vector &position )
  185. {
  186. m_attachedEntity = pEntity;
  187. pPhys->WorldToLocal( &m_localPosition, position );
  188. m_worldPosition = position;
  189. pPhys->GetDamping( NULL, &m_saveDamping );
  190. float damping = 2;
  191. pPhys->SetDamping( NULL, &damping );
  192. m_controller = physenv->CreateMotionController( this );
  193. m_controller->AttachObject( pPhys, true );
  194. m_controller->SetPriority( IPhysicsMotionController::HIGH_PRIORITY );
  195. SetTargetPosition( position );
  196. m_maxAcceleration = phys_gunforce.GetFloat() * pPhys->GetInvMass();
  197. m_targetRotation = pEntity->GetAbsAngles();
  198. float torque = phys_guntorque.GetFloat();
  199. m_maxAngularAcceleration = torque * pPhys->GetInvInertia();
  200. }
  201. void CGravControllerPoint::DetachEntity( void )
  202. {
  203. CBaseEntity *pEntity = m_attachedEntity;
  204. if ( pEntity )
  205. {
  206. IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
  207. if ( pPhys )
  208. {
  209. // on the odd chance that it's gone to sleep while under anti-gravity
  210. pPhys->Wake();
  211. pPhys->SetDamping( NULL, &m_saveDamping );
  212. }
  213. }
  214. m_attachedEntity = NULL;
  215. physenv->DestroyMotionController( m_controller );
  216. m_controller = NULL;
  217. // UNDONE: Does this help the networking?
  218. m_targetPosition = vec3_origin;
  219. m_worldPosition = vec3_origin;
  220. }
  221. void AxisAngleQAngle( const Vector &axis, float angle, QAngle &outAngles )
  222. {
  223. // map back to HL rotation axes
  224. outAngles.z = axis.x * angle;
  225. outAngles.x = axis.y * angle;
  226. outAngles.y = axis.z * angle;
  227. }
  228. IMotionEvent::simresult_e CGravControllerPoint::Simulate( IPhysicsMotionController *pController, IPhysicsObject *pObject, float deltaTime, Vector &linear, AngularImpulse &angular )
  229. {
  230. Vector vel;
  231. AngularImpulse angVel;
  232. float fracRemainingSimTime = 1.0;
  233. if ( m_timeToArrive > 0 )
  234. {
  235. fracRemainingSimTime *= deltaTime / m_timeToArrive;
  236. if ( fracRemainingSimTime > 1 )
  237. {
  238. fracRemainingSimTime = 1;
  239. }
  240. }
  241. m_timeToArrive -= deltaTime;
  242. if ( m_timeToArrive < 0 )
  243. {
  244. m_timeToArrive = 0;
  245. }
  246. float invDeltaTime = (1.0f / deltaTime);
  247. Vector world;
  248. pObject->LocalToWorld( &world, m_localPosition );
  249. m_worldPosition = world;
  250. pObject->GetVelocity( &vel, &angVel );
  251. //pObject->GetVelocityAtPoint( world, &vel );
  252. float damping = 1.0;
  253. world += vel * deltaTime * damping;
  254. Vector delta = (m_targetPosition - world) * fracRemainingSimTime * invDeltaTime;
  255. Vector alignDir;
  256. linear = vec3_origin;
  257. angular = vec3_origin;
  258. if ( m_align )
  259. {
  260. QAngle angles;
  261. Vector origin;
  262. Vector axis;
  263. AngularImpulse torque;
  264. pObject->GetShadowPosition( &origin, &angles );
  265. // align local normal to target normal
  266. VMatrix tmp = SetupMatrixOrgAngles( origin, angles );
  267. Vector worldNormal = tmp.VMul3x3( m_localAlignNormal );
  268. axis = CrossProduct( worldNormal, m_targetAlignNormal );
  269. float trig = VectorNormalize(axis);
  270. float alignRotation = RAD2DEG(asin(trig));
  271. axis *= alignRotation;
  272. if ( alignRotation < 10 )
  273. {
  274. float dot = DotProduct( worldNormal, m_targetAlignNormal );
  275. // probably 180 degrees off
  276. if ( dot < 0 )
  277. {
  278. if ( worldNormal.x < 0.5 )
  279. {
  280. axis.Init(10,0,0);
  281. }
  282. else
  283. {
  284. axis.Init(0,0,10);
  285. }
  286. alignRotation = 10;
  287. }
  288. }
  289. // Solve for the rotation around the target normal (at the local align pos) that will
  290. // move the grabbed spot to the destination.
  291. Vector worldRotCenter = tmp.VMul4x3( m_localAlignPosition );
  292. Vector rotSrc = world - worldRotCenter;
  293. Vector rotDest = m_targetPosition - worldRotCenter;
  294. // Get a basis in the plane perpendicular to m_targetAlignNormal
  295. Vector srcN = rotSrc;
  296. VectorNormalize( srcN );
  297. Vector tangent = CrossProduct( srcN, m_targetAlignNormal );
  298. float len = VectorNormalize( tangent );
  299. // needs at least ~5 degrees, or forget rotation (0.08 ~= sin(5))
  300. if ( len > 0.08 )
  301. {
  302. Vector binormal = CrossProduct( m_targetAlignNormal, tangent );
  303. // Now project the src & dest positions into that plane
  304. Vector planeSrc( DotProduct( rotSrc, tangent ), DotProduct( rotSrc, binormal ), 0 );
  305. Vector planeDest( DotProduct( rotDest, tangent ), DotProduct( rotDest, binormal ), 0 );
  306. float rotRadius = VectorNormalize( planeSrc );
  307. float destRadius = VectorNormalize( planeDest );
  308. if ( rotRadius > 0.1 )
  309. {
  310. if ( destRadius < rotRadius )
  311. {
  312. destRadius = rotRadius;
  313. }
  314. //float ratio = rotRadius / destRadius;
  315. float angleSrc = atan2( planeSrc.y, planeSrc.x );
  316. float angleDest = atan2( planeDest.y, planeDest.x );
  317. float angleDiff = angleDest - angleSrc;
  318. angleDiff = RAD2DEG(angleDiff);
  319. axis += m_targetAlignNormal * angleDiff;
  320. //world = m_targetPosition;// + rotDest * (1-ratio);
  321. // NDebugOverlay::Line( worldRotCenter, worldRotCenter-m_targetAlignNormal*50, 255, 0, 0, false, 0.1 );
  322. // NDebugOverlay::Line( worldRotCenter, worldRotCenter+tangent*50, 0, 255, 0, false, 0.1 );
  323. // NDebugOverlay::Line( worldRotCenter, worldRotCenter+binormal*50, 0, 0, 255, false, 0.1 );
  324. }
  325. }
  326. torque = WorldToLocalRotation( tmp, axis, 1 );
  327. torque *= fracRemainingSimTime * invDeltaTime;
  328. torque -= angVel * 1.0; // damping
  329. for ( int i = 0; i < 3; i++ )
  330. {
  331. if ( torque[i] > 0 )
  332. {
  333. if ( torque[i] > m_maxAngularAcceleration[i] )
  334. torque[i] = m_maxAngularAcceleration[i];
  335. }
  336. else
  337. {
  338. if ( torque[i] < -m_maxAngularAcceleration[i] )
  339. torque[i] = -m_maxAngularAcceleration[i];
  340. }
  341. }
  342. torque *= invDeltaTime;
  343. angular += torque;
  344. // Calculate an acceleration that pulls the object toward the constraint
  345. // When you're out of alignment, don't pull very hard
  346. float factor = fabsf(alignRotation);
  347. if ( factor < 5 )
  348. {
  349. factor = clamp( factor, 0, 5 ) * (1/5);
  350. alignDir = m_targetAlignPosition - worldRotCenter;
  351. // Limit movement to the part along m_targetAlignNormal if worldRotCenter is on the backside of
  352. // of the target plane (one inch epsilon)!
  353. float planeForward = DotProduct( alignDir, m_targetAlignNormal );
  354. if ( planeForward > 1 )
  355. {
  356. alignDir = m_targetAlignNormal * planeForward;
  357. }
  358. Vector accel = alignDir * invDeltaTime * fracRemainingSimTime * (1-factor) * 0.20 * invDeltaTime;
  359. float mag = accel.Length();
  360. if ( mag > m_maxAcceleration )
  361. {
  362. accel *= (m_maxAcceleration/mag);
  363. }
  364. linear += accel;
  365. }
  366. linear -= vel*damping*invDeltaTime;
  367. // UNDONE: Factor in the change in worldRotCenter due to applied torque!
  368. }
  369. else
  370. {
  371. // clamp future velocity to max speed
  372. Vector nextVel = delta + vel;
  373. float nextSpeed = nextVel.Length();
  374. if ( nextSpeed > m_maxVel )
  375. {
  376. nextVel *= (m_maxVel / nextSpeed);
  377. delta = nextVel - vel;
  378. }
  379. delta *= invDeltaTime;
  380. float linearAccel = delta.Length();
  381. if ( linearAccel > m_maxAcceleration )
  382. {
  383. delta *= m_maxAcceleration / linearAccel;
  384. }
  385. Vector accel;
  386. AngularImpulse angAccel;
  387. pObject->CalculateForceOffset( delta, world, &accel, &angAccel );
  388. linear += accel;
  389. angular += angAccel;
  390. }
  391. return SIM_GLOBAL_ACCELERATION;
  392. }
  393. struct pelletlist_t
  394. {
  395. DECLARE_SIMPLE_DATADESC();
  396. Vector localNormal; // normal in parent space
  397. CHandle<CGravityPellet> pellet;
  398. EHANDLE parent;
  399. };
  400. class CWeaponGravityGun : public CBaseCombatWeapon
  401. {
  402. DECLARE_DATADESC();
  403. public:
  404. DECLARE_CLASS( CWeaponGravityGun, CBaseCombatWeapon );
  405. CWeaponGravityGun();
  406. void Spawn( void );
  407. void OnRestore( void );
  408. void Precache( void );
  409. void PrimaryAttack( void );
  410. void SecondaryAttack( void );
  411. void WeaponIdle( void );
  412. void ItemPostFrame( void );
  413. virtual bool Holster( CBaseCombatWeapon *pSwitchingTo )
  414. {
  415. EffectDestroy();
  416. return BaseClass::Holster();
  417. }
  418. bool Reload( void );
  419. void Equip( CBaseCombatCharacter *pOwner )
  420. {
  421. // add constraint ammo
  422. pOwner->SetAmmoCount( MAX_PELLETS, m_iSecondaryAmmoType );
  423. BaseClass::Equip( pOwner );
  424. }
  425. void Drop(const Vector &vecVelocity)
  426. {
  427. CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  428. pOwner->SetAmmoCount( 0, m_iSecondaryAmmoType );
  429. // destroy all constraints
  430. BaseClass::Drop(vecVelocity);
  431. }
  432. bool HasAnyAmmo( void );
  433. void AttachObject( CBaseEntity *pEdict, const Vector& start, const Vector &end, float distance );
  434. void DetachObject( void );
  435. void EffectCreate( void );
  436. void EffectUpdate( void );
  437. void EffectDestroy( void );
  438. void SoundCreate( void );
  439. void SoundDestroy( void );
  440. void SoundStop( void );
  441. void SoundStart( void );
  442. void SoundUpdate( void );
  443. void AddPellet( CGravityPellet *pPellet, CBaseEntity *pParent, const Vector &surfaceNormal );
  444. void DeleteActivePellets();
  445. void SortPelletsForObject( CBaseEntity *pObject );
  446. void SetObjectPelletsColor( int r, int g, int b );
  447. void CreatePelletAttraction( float radius, CBaseEntity *pObject );
  448. IPhysicsObject *GetPelletPhysObject( int pelletIndex );
  449. void GetPelletWorldCoords( int pelletIndex, Vector *worldPos, Vector *worldNormal )
  450. {
  451. if ( worldPos )
  452. {
  453. *worldPos = m_activePellets[pelletIndex].pellet->GetAbsOrigin();
  454. }
  455. if ( worldNormal )
  456. {
  457. if ( m_activePellets[pelletIndex].parent )
  458. {
  459. EntityMatrix tmp;
  460. tmp.InitFromEntity( m_activePellets[pelletIndex].parent );
  461. *worldNormal = tmp.LocalToWorldRotation( m_activePellets[pelletIndex].localNormal );
  462. }
  463. else
  464. {
  465. *worldNormal = m_activePellets[pelletIndex].localNormal;
  466. }
  467. }
  468. }
  469. int ObjectCaps( void )
  470. {
  471. int caps = BaseClass::ObjectCaps();
  472. if ( m_active )
  473. {
  474. caps |= FCAP_DIRECTIONAL_USE;
  475. }
  476. return caps;
  477. }
  478. CBaseEntity *GetBeamEntity();
  479. DECLARE_SERVERCLASS();
  480. private:
  481. CNetworkVar( int, m_active );
  482. bool m_useDown;
  483. EHANDLE m_hObject;
  484. float m_distance;
  485. float m_movementLength;
  486. float m_lastYaw;
  487. int m_soundState;
  488. CNetworkVar( int, m_viewModelIndex );
  489. Vector m_originalObjectPosition;
  490. CGravControllerPoint m_gravCallback;
  491. pelletlist_t m_activePellets[MAX_PELLETS];
  492. int m_pelletCount;
  493. int m_objectPelletCount;
  494. int m_pelletHeld;
  495. int m_pelletAttract;
  496. float m_glueTime;
  497. CNetworkVar( bool, m_glueTouching );
  498. };
  499. IMPLEMENT_SERVERCLASS_ST( CWeaponGravityGun, DT_WeaponGravityGun )
  500. SendPropVector( SENDINFO_NAME(m_gravCallback.m_targetPosition, m_targetPosition), -1, SPROP_COORD ),
  501. SendPropVector( SENDINFO_NAME(m_gravCallback.m_worldPosition, m_worldPosition), -1, SPROP_COORD ),
  502. SendPropInt( SENDINFO(m_active), 1, SPROP_UNSIGNED ),
  503. SendPropInt( SENDINFO(m_glueTouching), 1, SPROP_UNSIGNED ),
  504. SendPropModelIndex( SENDINFO(m_viewModelIndex) ),
  505. END_SEND_TABLE()
  506. LINK_ENTITY_TO_CLASS( weapon_physgun, CWeaponGravityGun );
  507. PRECACHE_WEAPON_REGISTER(weapon_physgun);
  508. //---------------------------------------------------------
  509. // Save/Restore
  510. //---------------------------------------------------------
  511. BEGIN_SIMPLE_DATADESC( pelletlist_t )
  512. DEFINE_FIELD( localNormal, FIELD_VECTOR ),
  513. DEFINE_FIELD( pellet, FIELD_EHANDLE ),
  514. DEFINE_FIELD( parent, FIELD_EHANDLE ),
  515. END_DATADESC()
  516. BEGIN_DATADESC( CWeaponGravityGun )
  517. DEFINE_FIELD( m_active, FIELD_INTEGER ),
  518. DEFINE_FIELD( m_useDown, FIELD_BOOLEAN ),
  519. DEFINE_FIELD( m_hObject, FIELD_EHANDLE ),
  520. DEFINE_FIELD( m_distance, FIELD_FLOAT ),
  521. DEFINE_FIELD( m_movementLength, FIELD_FLOAT ),
  522. DEFINE_FIELD( m_lastYaw, FIELD_FLOAT ),
  523. DEFINE_FIELD( m_soundState, FIELD_INTEGER ),
  524. DEFINE_FIELD( m_viewModelIndex, FIELD_INTEGER ),
  525. DEFINE_FIELD( m_originalObjectPosition, FIELD_POSITION_VECTOR ),
  526. DEFINE_EMBEDDED( m_gravCallback ),
  527. // Physptrs can't be saved in embedded classes..
  528. DEFINE_PHYSPTR( m_gravCallback.m_controller ),
  529. DEFINE_EMBEDDED_AUTO_ARRAY( m_activePellets ),
  530. DEFINE_FIELD( m_pelletCount, FIELD_INTEGER ),
  531. DEFINE_FIELD( m_objectPelletCount, FIELD_INTEGER ),
  532. DEFINE_FIELD( m_pelletHeld, FIELD_INTEGER ),
  533. DEFINE_FIELD( m_pelletAttract, FIELD_INTEGER ),
  534. DEFINE_FIELD( m_glueTime, FIELD_TIME ),
  535. DEFINE_FIELD( m_glueTouching, FIELD_BOOLEAN ),
  536. END_DATADESC()
  537. enum physgun_soundstate { SS_SCANNING, SS_LOCKEDON };
  538. enum physgun_soundIndex { SI_LOCKEDON = 0, SI_SCANNING = 1, SI_LIGHTOBJECT = 2, SI_HEAVYOBJECT = 3, SI_ON, SI_OFF };
  539. //=========================================================
  540. //=========================================================
  541. CWeaponGravityGun::CWeaponGravityGun()
  542. {
  543. m_active = false;
  544. m_bFiresUnderwater = true;
  545. m_pelletAttract = -1;
  546. m_pelletHeld = -1;
  547. }
  548. //=========================================================
  549. //=========================================================
  550. void CWeaponGravityGun::Spawn( )
  551. {
  552. BaseClass::Spawn();
  553. // SetModel( GetWorldModel() );
  554. FallInit();
  555. }
  556. void CWeaponGravityGun::OnRestore( void )
  557. {
  558. BaseClass::OnRestore();
  559. if ( m_gravCallback.m_controller )
  560. {
  561. m_gravCallback.m_controller->SetEventHandler( &m_gravCallback );
  562. }
  563. }
  564. //=========================================================
  565. //=========================================================
  566. void CWeaponGravityGun::Precache( void )
  567. {
  568. BaseClass::Precache();
  569. g_physgunBeam = PrecacheModel(PHYSGUN_BEAM_SPRITE);
  570. PrecacheScriptSound( "Weapon_Physgun.Scanning" );
  571. PrecacheScriptSound( "Weapon_Physgun.LockedOn" );
  572. PrecacheScriptSound( "Weapon_Physgun.Scanning" );
  573. PrecacheScriptSound( "Weapon_Physgun.LightObject" );
  574. PrecacheScriptSound( "Weapon_Physgun.HeavyObject" );
  575. }
  576. void CWeaponGravityGun::EffectCreate( void )
  577. {
  578. EffectUpdate();
  579. m_active = true;
  580. }
  581. void CWeaponGravityGun::EffectUpdate( void )
  582. {
  583. Vector start, angles, forward, right;
  584. trace_t tr;
  585. CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  586. if ( !pOwner )
  587. return;
  588. m_viewModelIndex = pOwner->entindex();
  589. // Make sure I've got a view model
  590. CBaseViewModel *vm = pOwner->GetViewModel();
  591. if ( vm )
  592. {
  593. m_viewModelIndex = vm->entindex();
  594. }
  595. pOwner->EyeVectors( &forward, &right, NULL );
  596. start = pOwner->Weapon_ShootPosition();
  597. Vector end = start + forward * 4096;
  598. UTIL_TraceLine( start, end, MASK_SHOT, pOwner, COLLISION_GROUP_NONE, &tr );
  599. end = tr.endpos;
  600. float distance = tr.fraction * 4096;
  601. if ( tr.fraction != 1 )
  602. {
  603. // too close to the player, drop the object
  604. if ( distance < 36 )
  605. {
  606. DetachObject();
  607. return;
  608. }
  609. }
  610. if ( m_hObject == NULL && tr.DidHitNonWorldEntity() )
  611. {
  612. CBaseEntity *pEntity = tr.m_pEnt;
  613. // inform the object what was hit
  614. ClearMultiDamage();
  615. pEntity->DispatchTraceAttack( CTakeDamageInfo( pOwner, pOwner, 0, DMG_PHYSGUN ), forward, &tr );
  616. ApplyMultiDamage();
  617. AttachObject( pEntity, start, tr.endpos, distance );
  618. m_lastYaw = pOwner->EyeAngles().y;
  619. }
  620. // Add the incremental player yaw to the target transform
  621. matrix3x4_t curMatrix, incMatrix, nextMatrix;
  622. AngleMatrix( m_gravCallback.m_targetRotation, curMatrix );
  623. AngleMatrix( QAngle(0,pOwner->EyeAngles().y - m_lastYaw,0), incMatrix );
  624. ConcatTransforms( incMatrix, curMatrix, nextMatrix );
  625. MatrixAngles( nextMatrix, m_gravCallback.m_targetRotation );
  626. m_lastYaw = pOwner->EyeAngles().y;
  627. CBaseEntity *pObject = m_hObject;
  628. if ( pObject )
  629. {
  630. if ( m_useDown )
  631. {
  632. if ( pOwner->m_afButtonPressed & IN_USE )
  633. {
  634. m_useDown = false;
  635. }
  636. }
  637. else
  638. {
  639. if ( pOwner->m_afButtonPressed & IN_USE )
  640. {
  641. m_useDown = true;
  642. }
  643. }
  644. if ( m_useDown )
  645. {
  646. pOwner->SetPhysicsFlag( PFLAG_DIROVERRIDE, true );
  647. if ( pOwner->m_nButtons & IN_FORWARD )
  648. {
  649. m_distance = UTIL_Approach( 1024, m_distance, gpGlobals->frametime * 100 );
  650. }
  651. if ( pOwner->m_nButtons & IN_BACK )
  652. {
  653. m_distance = UTIL_Approach( 40, m_distance, gpGlobals->frametime * 100 );
  654. }
  655. }
  656. if ( pOwner->m_nButtons & IN_WEAPON1 )
  657. {
  658. m_distance = UTIL_Approach( 1024, m_distance, m_distance * 0.1 );
  659. }
  660. if ( pOwner->m_nButtons & IN_WEAPON2 )
  661. {
  662. m_distance = UTIL_Approach( 40, m_distance, m_distance * 0.1 );
  663. }
  664. // Send the object a physics damage message (0 damage). Some objects interpret this
  665. // as something else being in control of their physics temporarily.
  666. pObject->TakeDamage( CTakeDamageInfo( this, pOwner, 0, DMG_PHYSGUN ) );
  667. Vector newPosition = start + forward * m_distance;
  668. // 24 is a little larger than 16 * sqrt(2) (extent of player bbox)
  669. // HACKHACK: We do this so we can "ignore" the player and the object we're manipulating
  670. // If we had a filter for tracelines, we could simply filter both ents and start from "start"
  671. Vector awayfromPlayer = start + forward * 24;
  672. UTIL_TraceLine( start, awayfromPlayer, MASK_SOLID, pOwner, COLLISION_GROUP_NONE, &tr );
  673. if ( tr.fraction == 1 )
  674. {
  675. UTIL_TraceLine( awayfromPlayer, newPosition, MASK_SOLID, pObject, COLLISION_GROUP_NONE, &tr );
  676. Vector dir = tr.endpos - newPosition;
  677. float distance = VectorNormalize(dir);
  678. float maxDist = m_gravCallback.m_maxVel * gpGlobals->frametime;
  679. if ( distance > maxDist )
  680. {
  681. newPosition += dir * maxDist;
  682. }
  683. else
  684. {
  685. newPosition = tr.endpos;
  686. }
  687. }
  688. else
  689. {
  690. newPosition = tr.endpos;
  691. }
  692. CreatePelletAttraction( phys_gunglueradius.GetFloat(), pObject );
  693. // If I'm looking more than 20 degrees away from the glue point, then give up
  694. // This lets the player "gesture" for the glue to let go.
  695. Vector pelletDir = m_gravCallback.m_worldPosition - start;
  696. VectorNormalize(pelletDir);
  697. if ( DotProduct( pelletDir, forward ) < 0.939 ) // 0.939 ~= cos(20deg)
  698. {
  699. // lose attach for 2 seconds if you're too far away
  700. m_glueTime = gpGlobals->curtime + 1;
  701. }
  702. if ( m_pelletHeld >= 0 && gpGlobals->curtime > m_glueTime )
  703. {
  704. CGravityPellet *pPelletAttract = m_activePellets[m_pelletAttract].pellet;
  705. g_pEffects->Sparks( pPelletAttract->GetAbsOrigin() );
  706. }
  707. m_gravCallback.SetTargetPosition( newPosition );
  708. Vector dir = (newPosition - pObject->GetLocalOrigin());
  709. m_movementLength = dir.Length();
  710. }
  711. else
  712. {
  713. m_gravCallback.SetTargetPosition( end );
  714. }
  715. if ( m_pelletHeld >= 0 && gpGlobals->curtime > m_glueTime )
  716. {
  717. Vector worldNormal, worldPos;
  718. GetPelletWorldCoords( m_pelletAttract, &worldPos, &worldNormal );
  719. m_gravCallback.SetAutoAlign( m_activePellets[m_pelletHeld].localNormal, m_activePellets[m_pelletHeld].pellet->GetLocalOrigin(), worldNormal, worldPos );
  720. }
  721. else
  722. {
  723. m_gravCallback.ClearAutoAlign();
  724. }
  725. }
  726. void CWeaponGravityGun::SoundCreate( void )
  727. {
  728. m_soundState = SS_SCANNING;
  729. SoundStart();
  730. }
  731. void CWeaponGravityGun::SoundDestroy( void )
  732. {
  733. SoundStop();
  734. }
  735. void CWeaponGravityGun::SoundStop( void )
  736. {
  737. switch( m_soundState )
  738. {
  739. case SS_SCANNING:
  740. GetOwner()->StopSound( "Weapon_Physgun.Scanning" );
  741. break;
  742. case SS_LOCKEDON:
  743. GetOwner()->StopSound( "Weapon_Physgun.Scanning" );
  744. GetOwner()->StopSound( "Weapon_Physgun.LockedOn" );
  745. GetOwner()->StopSound( "Weapon_Physgun.LightObject" );
  746. GetOwner()->StopSound( "Weapon_Physgun.HeavyObject" );
  747. break;
  748. }
  749. }
  750. //-----------------------------------------------------------------------------
  751. // Purpose: returns the linear fraction of value between low & high (0.0 - 1.0) * scale
  752. // e.g. UTIL_LineFraction( 1.5, 1, 2, 1 ); will return 0.5 since 1.5 is
  753. // halfway between 1 and 2
  754. // Input : value - a value between low & high (clamped)
  755. // low - the value that maps to zero
  756. // high - the value that maps to "scale"
  757. // scale - the output scale
  758. // Output : parametric fraction between low & high
  759. //-----------------------------------------------------------------------------
  760. static float UTIL_LineFraction( float value, float low, float high, float scale )
  761. {
  762. if ( value < low )
  763. value = low;
  764. if ( value > high )
  765. value = high;
  766. float delta = high - low;
  767. if ( delta == 0 )
  768. return 0;
  769. return scale * (value-low) / delta;
  770. }
  771. void CWeaponGravityGun::SoundStart( void )
  772. {
  773. CPASAttenuationFilter filter( GetOwner() );
  774. filter.MakeReliable();
  775. switch( m_soundState )
  776. {
  777. case SS_SCANNING:
  778. {
  779. EmitSound( filter, GetOwner()->entindex(), "Weapon_Physgun.Scanning" );
  780. }
  781. break;
  782. case SS_LOCKEDON:
  783. {
  784. // BUGBUG - If you start a sound with a pitch of 100, the pitch shift doesn't work!
  785. EmitSound( filter, GetOwner()->entindex(), "Weapon_Physgun.LockedOn" );
  786. EmitSound( filter, GetOwner()->entindex(), "Weapon_Physgun.Scanning" );
  787. EmitSound( filter, GetOwner()->entindex(), "Weapon_Physgun.LightObject" );
  788. EmitSound( filter, GetOwner()->entindex(), "Weapon_Physgun.HeavyObject" );
  789. }
  790. break;
  791. }
  792. // volume, att, flags, pitch
  793. }
  794. void CWeaponGravityGun::SoundUpdate( void )
  795. {
  796. int newState;
  797. if ( m_hObject )
  798. newState = SS_LOCKEDON;
  799. else
  800. newState = SS_SCANNING;
  801. if ( newState != m_soundState )
  802. {
  803. SoundStop();
  804. m_soundState = newState;
  805. SoundStart();
  806. }
  807. switch( m_soundState )
  808. {
  809. case SS_SCANNING:
  810. break;
  811. case SS_LOCKEDON:
  812. {
  813. CPASAttenuationFilter filter( GetOwner() );
  814. filter.MakeReliable();
  815. float height = m_hObject->GetAbsOrigin().z - m_originalObjectPosition.z;
  816. // go from pitch 90 to 150 over a height of 500
  817. int pitch = 90 + (int)UTIL_LineFraction( height, 0, 500, 60 );
  818. CSoundParameters params;
  819. if ( GetParametersForSound( "Weapon_Physgun.LockedOn", params, NULL ) )
  820. {
  821. EmitSound_t ep( params );
  822. ep.m_nFlags = SND_CHANGE_VOL | SND_CHANGE_PITCH;
  823. ep.m_nPitch = pitch;
  824. EmitSound( filter, GetOwner()->entindex(), ep );
  825. }
  826. // attenutate the movement sounds over 200 units of movement
  827. float distance = UTIL_LineFraction( m_movementLength, 0, 200, 1.0 );
  828. // blend the "mass" sounds between 50 and 500 kg
  829. IPhysicsObject *pPhys = m_hObject->VPhysicsGetObject();
  830. float fade = UTIL_LineFraction( pPhys->GetMass(), 50, 500, 1.0 );
  831. if ( GetParametersForSound( "Weapon_Physgun.LightObject", params, NULL ) )
  832. {
  833. EmitSound_t ep( params );
  834. ep.m_nFlags = SND_CHANGE_VOL;
  835. ep.m_flVolume = fade * distance;
  836. EmitSound( filter, GetOwner()->entindex(), ep );
  837. }
  838. if ( GetParametersForSound( "Weapon_Physgun.HeavyObject", params, NULL ) )
  839. {
  840. EmitSound_t ep( params );
  841. ep.m_nFlags = SND_CHANGE_VOL;
  842. ep.m_flVolume = (1.0 - fade) * distance;
  843. EmitSound( filter, GetOwner()->entindex(), ep );
  844. }
  845. }
  846. break;
  847. }
  848. }
  849. void CWeaponGravityGun::AddPellet( CGravityPellet *pPellet, CBaseEntity *pAttach, const Vector &surfaceNormal )
  850. {
  851. Assert(m_pelletCount<MAX_PELLETS);
  852. m_activePellets[m_pelletCount].localNormal = surfaceNormal;
  853. if ( pAttach )
  854. {
  855. EntityMatrix tmp;
  856. tmp.InitFromEntity( pAttach );
  857. m_activePellets[m_pelletCount].localNormal = tmp.WorldToLocalRotation( surfaceNormal );
  858. }
  859. m_activePellets[m_pelletCount].pellet = pPellet;
  860. m_activePellets[m_pelletCount].parent = pAttach;
  861. m_pelletCount++;
  862. }
  863. void CWeaponGravityGun::SortPelletsForObject( CBaseEntity *pObject )
  864. {
  865. m_objectPelletCount = 0;
  866. for ( int i = 0; i < m_pelletCount; i++ )
  867. {
  868. // move pellets attached to the active object to the front of the list
  869. if ( m_activePellets[i].parent == pObject && !m_activePellets[i].pellet->IsInert() )
  870. {
  871. if ( i != 0 )
  872. {
  873. pelletlist_t tmp = m_activePellets[m_objectPelletCount];
  874. m_activePellets[m_objectPelletCount] = m_activePellets[i];
  875. m_activePellets[i] = tmp;
  876. }
  877. m_objectPelletCount++;
  878. }
  879. }
  880. SetObjectPelletsColor( 192, 255, 192 );
  881. }
  882. void CWeaponGravityGun::SetObjectPelletsColor( int r, int g, int b )
  883. {
  884. color32 color;
  885. color.r = r;
  886. color.g = g;
  887. color.b = b;
  888. color.a = 255;
  889. for ( int i = 0; i < m_objectPelletCount; i++ )
  890. {
  891. CGravityPellet *pPellet = m_activePellets[i].pellet;
  892. if ( !pPellet || pPellet->IsInert() )
  893. continue;
  894. pPellet->m_clrRender = color;
  895. }
  896. }
  897. CBaseEntity *CWeaponGravityGun::GetBeamEntity()
  898. {
  899. CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  900. if ( !pOwner )
  901. return NULL;
  902. // Make sure I've got a view model
  903. CBaseViewModel *vm = pOwner->GetViewModel();
  904. if ( vm )
  905. return vm;
  906. return pOwner;
  907. }
  908. void CWeaponGravityGun::DeleteActivePellets()
  909. {
  910. CBaseEntity *pEnt = GetBeamEntity();
  911. for ( int i = 0; i < m_pelletCount; i++ )
  912. {
  913. CGravityPellet *pPellet = m_activePellets[i].pellet;
  914. if ( !pPellet )
  915. continue;
  916. Vector forward;
  917. AngleVectors( pPellet->GetAbsAngles(), &forward );
  918. g_pEffects->Dust( pPellet->GetAbsOrigin(), forward, 32, 30 );
  919. // UNDONE: Probably should just do this client side
  920. CBeam *pBeam = CBeam::BeamCreate( PHYSGUN_BEAM_SPRITE, 1.5 );
  921. pBeam->PointEntInit( pPellet->GetAbsOrigin(), pEnt );
  922. pBeam->SetEndAttachment( 1 );
  923. pBeam->SetBrightness( 255 );
  924. pBeam->SetColor( 255, 0, 0 );
  925. pBeam->RelinkBeam();
  926. pBeam->LiveForTime( 0.1 );
  927. UTIL_Remove( pPellet );
  928. }
  929. m_pelletCount = 0;
  930. }
  931. void CWeaponGravityGun::CreatePelletAttraction( float radius, CBaseEntity *pObject )
  932. {
  933. int nearPellet = -1;
  934. int objectPellet = -1;
  935. float best = radius*radius;
  936. // already have a pellet, check for in range
  937. if ( m_pelletAttract >= 0 )
  938. {
  939. Vector attract, held;
  940. GetPelletWorldCoords( m_pelletAttract, &attract, NULL );
  941. GetPelletWorldCoords( m_pelletHeld, &held, NULL );
  942. float dist = (attract - held).Length();
  943. if ( dist < radius * 2 )
  944. {
  945. nearPellet = m_pelletAttract;
  946. objectPellet = m_pelletHeld;
  947. best = dist * dist;
  948. }
  949. }
  950. if ( nearPellet < 0 )
  951. {
  952. for ( int i = 0; i < m_objectPelletCount; i++ )
  953. {
  954. CGravityPellet *pPellet = m_activePellets[i].pellet;
  955. if ( !pPellet )
  956. continue;
  957. for ( int j = m_objectPelletCount; j < m_pelletCount; j++ )
  958. {
  959. CGravityPellet *pTest = m_activePellets[j].pellet;
  960. if ( !pTest )
  961. continue;
  962. if ( pTest->IsInert() )
  963. continue;
  964. float distSqr = (pTest->GetAbsOrigin() - pPellet->GetAbsOrigin()).LengthSqr();
  965. if ( distSqr < best )
  966. {
  967. Vector worldPos, worldNormal;
  968. GetPelletWorldCoords( j, &worldPos, &worldNormal );
  969. // don't attract backside pellets (unless current pellet - prevent oscillation)
  970. float dist = DotProduct( worldPos, worldNormal );
  971. if ( m_pelletAttract == j || DotProduct( pPellet->GetAbsOrigin(), worldNormal ) - dist >= 0 )
  972. {
  973. best = distSqr;
  974. nearPellet = j;
  975. objectPellet = i;
  976. }
  977. }
  978. }
  979. }
  980. }
  981. m_glueTouching = false;
  982. if ( nearPellet < 0 || objectPellet < 0 )
  983. {
  984. m_pelletAttract = -1;
  985. m_pelletHeld = -1;
  986. return;
  987. }
  988. if ( nearPellet != m_pelletAttract || objectPellet != m_pelletHeld )
  989. {
  990. m_glueTime = gpGlobals->curtime;
  991. m_pelletAttract = nearPellet;
  992. m_pelletHeld = objectPellet;
  993. }
  994. // check for bonding
  995. if ( best < 3*3 )
  996. {
  997. // This makes the pull towards the pellet stop getting stronger since some part of
  998. // the object is touching
  999. m_glueTouching = true;
  1000. }
  1001. }
  1002. IPhysicsObject *CWeaponGravityGun::GetPelletPhysObject( int pelletIndex )
  1003. {
  1004. if ( pelletIndex < 0 )
  1005. return NULL;
  1006. CBaseEntity *pEntity = m_activePellets[pelletIndex].parent;
  1007. if ( pEntity )
  1008. return pEntity->VPhysicsGetObject();
  1009. return g_PhysWorldObject;
  1010. }
  1011. void CWeaponGravityGun::EffectDestroy( void )
  1012. {
  1013. m_active = false;
  1014. SoundStop();
  1015. DetachObject();
  1016. }
  1017. void CWeaponGravityGun::DetachObject( void )
  1018. {
  1019. m_pelletHeld = -1;
  1020. m_pelletAttract = -1;
  1021. m_glueTouching = false;
  1022. SetObjectPelletsColor( 255, 0, 0 );
  1023. m_objectPelletCount = 0;
  1024. if ( m_hObject )
  1025. {
  1026. CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  1027. Pickup_OnPhysGunDrop( m_hObject, pOwner, DROPPED_BY_CANNON );
  1028. m_gravCallback.DetachEntity();
  1029. m_hObject = NULL;
  1030. }
  1031. }
  1032. void CWeaponGravityGun::AttachObject( CBaseEntity *pObject, const Vector& start, const Vector &end, float distance )
  1033. {
  1034. m_hObject = pObject;
  1035. m_useDown = false;
  1036. IPhysicsObject *pPhysics = pObject ? (pObject->VPhysicsGetObject()) : NULL;
  1037. if ( pPhysics && pObject->GetMoveType() == MOVETYPE_VPHYSICS )
  1038. {
  1039. m_distance = distance;
  1040. m_gravCallback.AttachEntity( pObject, pPhysics, end );
  1041. float mass = pPhysics->GetMass();
  1042. Msg( "Object mass: %.2f lbs (%.2f kg)\n", kg2lbs(mass), mass );
  1043. float vel = phys_gunvel.GetFloat();
  1044. if ( mass > phys_gunmass.GetFloat() )
  1045. {
  1046. vel = (vel*phys_gunmass.GetFloat())/mass;
  1047. }
  1048. m_gravCallback.SetMaxVelocity( vel );
  1049. // Msg( "Object mass: %.2f lbs (%.2f kg) %f %f %f\n", kg2lbs(mass), mass, pObject->GetAbsOrigin().x, pObject->GetAbsOrigin().y, pObject->GetAbsOrigin().z );
  1050. // Msg( "ANG: %f %f %f\n", pObject->GetAbsAngles().x, pObject->GetAbsAngles().y, pObject->GetAbsAngles().z );
  1051. m_originalObjectPosition = pObject->GetAbsOrigin();
  1052. m_pelletAttract = -1;
  1053. m_pelletHeld = -1;
  1054. pPhysics->Wake();
  1055. SortPelletsForObject( pObject );
  1056. CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  1057. if( pOwner )
  1058. {
  1059. Pickup_OnPhysGunPickup( pObject, pOwner );
  1060. }
  1061. }
  1062. else
  1063. {
  1064. m_hObject = NULL;
  1065. }
  1066. }
  1067. //=========================================================
  1068. //=========================================================
  1069. void CWeaponGravityGun::PrimaryAttack( void )
  1070. {
  1071. if ( !m_active )
  1072. {
  1073. SendWeaponAnim( ACT_VM_PRIMARYATTACK );
  1074. EffectCreate();
  1075. SoundCreate();
  1076. }
  1077. else
  1078. {
  1079. EffectUpdate();
  1080. SoundUpdate();
  1081. }
  1082. }
  1083. void CWeaponGravityGun::SecondaryAttack( void )
  1084. {
  1085. m_flNextSecondaryAttack = gpGlobals->curtime + 0.1;
  1086. if ( m_active )
  1087. {
  1088. EffectDestroy();
  1089. SoundDestroy();
  1090. return;
  1091. }
  1092. CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  1093. Assert( pOwner );
  1094. if ( pOwner->GetAmmoCount(m_iSecondaryAmmoType) <= 0 )
  1095. return;
  1096. m_viewModelIndex = pOwner->entindex();
  1097. // Make sure I've got a view model
  1098. CBaseViewModel *vm = pOwner->GetViewModel();
  1099. if ( vm )
  1100. {
  1101. m_viewModelIndex = vm->entindex();
  1102. }
  1103. Vector forward;
  1104. pOwner->EyeVectors( &forward );
  1105. Vector start = pOwner->Weapon_ShootPosition();
  1106. Vector end = start + forward * 4096;
  1107. trace_t tr;
  1108. UTIL_TraceLine( start, end, MASK_SHOT, pOwner, COLLISION_GROUP_NONE, &tr );
  1109. if ( tr.fraction == 1.0 || (tr.surface.flags & SURF_SKY) )
  1110. return;
  1111. CBaseEntity *pHit = tr.m_pEnt;
  1112. if ( pHit->entindex() == 0 )
  1113. {
  1114. pHit = NULL;
  1115. }
  1116. else
  1117. {
  1118. // if the object has no physics object, or isn't a physprop or brush entity, then don't glue
  1119. if ( !pHit->VPhysicsGetObject() || pHit->GetMoveType() != MOVETYPE_VPHYSICS )
  1120. return;
  1121. }
  1122. QAngle angles;
  1123. WeaponSound( SINGLE );
  1124. pOwner->RemoveAmmo( 1, m_iSecondaryAmmoType );
  1125. VectorAngles( tr.plane.normal, angles );
  1126. Vector endPoint = tr.endpos + tr.plane.normal;
  1127. CGravityPellet *pPellet = (CGravityPellet *)CBaseEntity::Create( "gravity_pellet", endPoint, angles, this );
  1128. if ( pHit )
  1129. {
  1130. pPellet->SetParent( pHit );
  1131. }
  1132. AddPellet( pPellet, pHit, tr.plane.normal );
  1133. // UNDONE: Probably should just do this client side
  1134. CBaseEntity *pEnt = GetBeamEntity();
  1135. CBeam *pBeam = CBeam::BeamCreate( PHYSGUN_BEAM_SPRITE, 1.5 );
  1136. pBeam->PointEntInit( endPoint, pEnt );
  1137. pBeam->SetEndAttachment( 1 );
  1138. pBeam->SetBrightness( 255 );
  1139. pBeam->SetColor( 255, 0, 0 );
  1140. pBeam->RelinkBeam();
  1141. pBeam->LiveForTime( 0.1 );
  1142. }
  1143. void CWeaponGravityGun::WeaponIdle( void )
  1144. {
  1145. if ( HasWeaponIdleTimeElapsed() )
  1146. {
  1147. SendWeaponAnim( ACT_VM_IDLE );
  1148. if ( m_active )
  1149. {
  1150. CBaseEntity *pObject = m_hObject;
  1151. // pellet is touching object, so glue it
  1152. if ( pObject && m_glueTouching )
  1153. {
  1154. CGravityPellet *pPellet = m_activePellets[m_pelletAttract].pellet;
  1155. if ( pPellet->MakeConstraint( pObject ) )
  1156. {
  1157. WeaponSound( SPECIAL1 );
  1158. m_flNextPrimaryAttack = gpGlobals->curtime + 0.75;
  1159. m_activePellets[m_pelletHeld].pellet->MakeInert();
  1160. }
  1161. }
  1162. EffectDestroy();
  1163. SoundDestroy();
  1164. }
  1165. }
  1166. }
  1167. void CWeaponGravityGun::ItemPostFrame( void )
  1168. {
  1169. CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  1170. if (!pOwner)
  1171. return;
  1172. if ( pOwner->m_afButtonPressed & IN_ATTACK2 )
  1173. {
  1174. SecondaryAttack();
  1175. }
  1176. else if ( pOwner->m_nButtons & IN_ATTACK )
  1177. {
  1178. PrimaryAttack();
  1179. }
  1180. else if ( pOwner->m_afButtonPressed & IN_RELOAD )
  1181. {
  1182. Reload();
  1183. }
  1184. // -----------------------
  1185. // No buttons down
  1186. // -----------------------
  1187. else
  1188. {
  1189. WeaponIdle( );
  1190. return;
  1191. }
  1192. }
  1193. //-----------------------------------------------------------------------------
  1194. // Purpose:
  1195. // Output : Returns true on success, false on failure.
  1196. //-----------------------------------------------------------------------------
  1197. bool CWeaponGravityGun::HasAnyAmmo( void )
  1198. {
  1199. //Always report that we have ammo
  1200. return true;
  1201. }
  1202. //=========================================================
  1203. //=========================================================
  1204. bool CWeaponGravityGun::Reload( void )
  1205. {
  1206. CBasePlayer *pOwner = ToBasePlayer( GetOwner() );
  1207. if ( pOwner->GetAmmoCount(m_iSecondaryAmmoType) != MAX_PELLETS )
  1208. {
  1209. pOwner->SetAmmoCount( MAX_PELLETS, m_iSecondaryAmmoType );
  1210. DeleteActivePellets();
  1211. WeaponSound( RELOAD );
  1212. return true;
  1213. }
  1214. return false;
  1215. }
  1216. #define NUM_COLLISION_TESTS 2500
  1217. void CC_CollisionTest( const CCommand &args )
  1218. {
  1219. if ( !physenv )
  1220. return;
  1221. Msg( "Testing collision system\n" );
  1222. int i;
  1223. CBaseEntity *pSpot = gEntList.FindEntityByClassname( NULL, "info_player_start");
  1224. Vector start = pSpot->GetAbsOrigin();
  1225. static Vector *targets = NULL;
  1226. static bool first = true;
  1227. static float test[2] = {1,1};
  1228. if ( first )
  1229. {
  1230. targets = new Vector[NUM_COLLISION_TESTS];
  1231. float radius = 0;
  1232. float theta = 0;
  1233. float phi = 0;
  1234. for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
  1235. {
  1236. radius += NUM_COLLISION_TESTS * 123.123;
  1237. radius = fabs(fmod(radius, 128));
  1238. theta += NUM_COLLISION_TESTS * 76.76;
  1239. theta = fabs(fmod(theta, DEG2RAD(360)));
  1240. phi += NUM_COLLISION_TESTS * 1997.99;
  1241. phi = fabs(fmod(phi, DEG2RAD(180)));
  1242. float st, ct, sp, cp;
  1243. SinCos( theta, &st, &ct );
  1244. SinCos( phi, &sp, &cp );
  1245. targets[i].x = radius * ct * sp;
  1246. targets[i].y = radius * st * sp;
  1247. targets[i].z = radius * cp;
  1248. // make the trace 1024 units long
  1249. Vector dir = targets[i] - start;
  1250. VectorNormalize(dir);
  1251. targets[i] = start + dir * 1024;
  1252. }
  1253. first = false;
  1254. }
  1255. //Vector results[NUM_COLLISION_TESTS];
  1256. int testType = 0;
  1257. if ( args.ArgC() >= 2 )
  1258. {
  1259. testType = atoi( args[1] );
  1260. }
  1261. float duration = 0;
  1262. Vector size[2];
  1263. size[0].Init(0,0,0);
  1264. size[1].Init(16,16,16);
  1265. unsigned int dots = 0;
  1266. for ( int j = 0; j < 2; j++ )
  1267. {
  1268. float startTime = engine->Time();
  1269. if ( testType == 1 )
  1270. {
  1271. const CPhysCollide *pCollide = g_PhysWorldObject->GetCollide();
  1272. trace_t tr;
  1273. for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
  1274. {
  1275. physcollision->TraceBox( start, targets[i], -size[j], size[j], pCollide, vec3_origin, vec3_angle, &tr );
  1276. dots += physcollision->ReadStat(0);
  1277. //results[i] = tr.endpos;
  1278. }
  1279. }
  1280. else
  1281. {
  1282. testType = 0;
  1283. CBaseEntity *pWorld = GetContainingEntity( INDEXENT(0) );
  1284. trace_t tr;
  1285. for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
  1286. {
  1287. UTIL_TraceModel( start, targets[i], -size[j], size[j], pWorld, COLLISION_GROUP_NONE, &tr );
  1288. //results[i] = tr.endpos;
  1289. }
  1290. }
  1291. duration += engine->Time() - startTime;
  1292. }
  1293. test[testType] = duration;
  1294. Msg("%d collisions in %.2f ms (%u dots)\n", NUM_COLLISION_TESTS, duration*1000, dots );
  1295. Msg("Current speed ratio: %.2fX BSP:JGJK\n", test[1] / test[0] );
  1296. #if 0
  1297. int red = 255, green = 0, blue = 0;
  1298. for ( i = 0; i < NUM_COLLISION_TESTS; i++ )
  1299. {
  1300. NDebugOverlay::Line( start, results[i], red, green, blue, false, 2 );
  1301. }
  1302. #endif
  1303. }
  1304. static ConCommand collision_test("collision_test", CC_CollisionTest, "Tests collision system", FCVAR_CHEAT );