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.

1030 lines
26 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //===========================================================================//
  6. #include "cbase.h"
  7. #include "physpropclientside.h"
  8. #include "vcollide_parse.h"
  9. #include "mapentities_shared.h"
  10. #include "gamestringpool.h"
  11. #include "props_shared.h"
  12. #include "c_te_effect_dispatch.h"
  13. #include "datacache/imdlcache.h"
  14. #include "view.h"
  15. #include "tier0/vprof.h"
  16. // memdbgon must be the last include file in a .cpp file!!!
  17. #include "tier0/memdbgon.h"
  18. #define FADEOUT_TIME 1.0f
  19. ConVar cl_phys_props_max( "cl_phys_props_max", "300", 0, "Maximum clientside physic props" );
  20. ConVar r_propsmaxdist( "r_propsmaxdist", "1200", 0, "Maximum visible distance" );
  21. ConVar cl_phys_props_enable( "cl_phys_props_enable", "1", 0, "Disable clientside physics props (must be set before loading a level)." );
  22. ConVar cl_phys_props_respawndist( "cl_phys_props_respawndist", "1500", 0, "Minimum distance from the player that a clientside prop must be before it's allowed to respawn." );
  23. ConVar cl_phys_props_respawnrate( "cl_phys_props_respawnrate", "60", 0, "Time, in seconds, between clientside prop respawns." );
  24. //////////////////////////////////////////////////////////////////////
  25. // Construction/Destruction
  26. //////////////////////////////////////////////////////////////////////
  27. static int PropBreakablePrecacheAll( int modelIndex )
  28. {
  29. CUtlVector<breakmodel_t> list;
  30. BreakModelList( list, modelIndex, COLLISION_GROUP_NONE, 0 );
  31. return list.Count();
  32. }
  33. static CUtlVector<C_PhysPropClientside*> s_PhysPropList;
  34. static CUtlVector<C_FuncPhysicsRespawnZone*> s_RespawnZoneList;
  35. C_PhysPropClientside *C_PhysPropClientside::CreateNew( bool bForce )
  36. {
  37. if ( (s_PhysPropList.Count() >= cl_phys_props_max.GetInt()) && !bForce )
  38. {
  39. DevMsg("Warning! Client physic props overflow *max %i).\n", cl_phys_props_max.GetInt() );
  40. return NULL;
  41. }
  42. return new C_PhysPropClientside();
  43. }
  44. C_PhysPropClientside::C_PhysPropClientside()
  45. {
  46. m_fDeathTime = -1;
  47. m_impactEnergyScale = 1.0f;
  48. m_iHealth = 0;
  49. m_iPhysicsMode = PHYSICS_MULTIPLAYER_AUTODETECT;
  50. m_flTouchDelta = 0;
  51. m_pRespawnZone = NULL;
  52. s_PhysPropList.AddToTail( this );
  53. }
  54. C_PhysPropClientside::~C_PhysPropClientside()
  55. {
  56. if ( m_pRespawnZone )
  57. {
  58. m_pRespawnZone->PropDestroyed( this );
  59. }
  60. PhysCleanupFrictionSounds( this );
  61. VPhysicsDestroyObject();
  62. s_PhysPropList.FindAndRemove( this );
  63. }
  64. void C_PhysPropClientside::SetPhysicsMode(int iMode)
  65. {
  66. if ( m_iPhysicsMode == PHYSICS_MULTIPLAYER_AUTODETECT )
  67. m_iPhysicsMode = iMode;
  68. }
  69. //-----------------------------------------------------------------------------
  70. // Should we collide?
  71. //-----------------------------------------------------------------------------
  72. bool C_PhysPropClientside::KeyValue( const char *szKeyName, const char *szValue )
  73. {
  74. if (FStrEq(szKeyName, "physdamagescale"))
  75. {
  76. m_impactEnergyScale = atof(szValue);
  77. }
  78. else if ( FStrEq(szKeyName, "health") )
  79. {
  80. m_iHealth = Q_atoi(szValue);
  81. }
  82. else if (FStrEq(szKeyName, "spawnflags"))
  83. {
  84. m_spawnflags = Q_atoi(szValue);
  85. }
  86. else if (FStrEq(szKeyName, "model"))
  87. {
  88. SetModelName( AllocPooledString( szValue ) );
  89. }
  90. else if (FStrEq(szKeyName, "fademaxdist"))
  91. {
  92. m_fadeMaxDist = Q_atof(szValue);
  93. }
  94. else if (FStrEq(szKeyName, "fademindist"))
  95. {
  96. m_fadeMinDist = Q_atof(szValue);
  97. }
  98. else if (FStrEq(szKeyName, "fadescale"))
  99. {
  100. m_flFadeScale = Q_atof(szValue);
  101. }
  102. else if (FStrEq(szKeyName, "inertiaScale"))
  103. {
  104. m_inertiaScale = Q_atof(szValue);
  105. }
  106. else if (FStrEq(szKeyName, "skin"))
  107. {
  108. m_nSkin = Q_atoi(szValue);
  109. }
  110. else if (FStrEq(szKeyName, "physicsmode"))
  111. {
  112. m_iPhysicsMode = Q_atoi(szValue);
  113. }
  114. else
  115. {
  116. if ( !BaseClass::KeyValue( szKeyName, szValue ) )
  117. {
  118. // key hasn't been handled
  119. return false;
  120. }
  121. }
  122. return true;
  123. }
  124. //-----------------------------------------------------------------------------
  125. // Purpose:
  126. // Input : *pOther -
  127. //-----------------------------------------------------------------------------
  128. void C_PhysPropClientside::StartTouch( C_BaseEntity *pOther )
  129. {
  130. // Limit the amount of times we can bounce
  131. if ( m_flTouchDelta < gpGlobals->curtime )
  132. {
  133. HitSurface( pOther );
  134. m_flTouchDelta = gpGlobals->curtime + 0.1f;
  135. }
  136. BaseClass::StartTouch( pOther );
  137. }
  138. //-----------------------------------------------------------------------------
  139. // Purpose:
  140. // Input : *pOther -
  141. //-----------------------------------------------------------------------------
  142. void C_PhysPropClientside::HitSurface( C_BaseEntity *pOther )
  143. {
  144. if ( HasInteraction( PROPINTER_WORLD_BLOODSPLAT ) )
  145. {
  146. trace_t tr;
  147. tr = BaseClass::GetTouchTrace();
  148. if ( tr.m_pEnt )
  149. {
  150. UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED );
  151. }
  152. }
  153. }
  154. void C_PhysPropClientside::RecreateAll()
  155. {
  156. DestroyAll();
  157. if ( cl_phys_props_enable.GetInt() )
  158. {
  159. ParseAllEntities( engine->GetMapEntitiesString() );
  160. InitializePropRespawnZones();
  161. }
  162. }
  163. void C_PhysPropClientside::DestroyAll()
  164. {
  165. while (s_PhysPropList.Count() > 0 )
  166. {
  167. C_PhysPropClientside *p = s_PhysPropList[0];
  168. p->Release();
  169. }
  170. while (s_RespawnZoneList.Count() > 0)
  171. {
  172. C_FuncPhysicsRespawnZone *p = s_RespawnZoneList[0];
  173. p->Release();
  174. }
  175. }
  176. void C_PhysPropClientside::SetRespawnZone( C_FuncPhysicsRespawnZone *pZone )
  177. {
  178. m_pRespawnZone = pZone;
  179. }
  180. //-----------------------------------------------------------------------------
  181. // Purpose: Parse this prop's data from the model, if it has a keyvalues section.
  182. // Returns true only if this prop is using a model that has a prop_data section that's invalid.
  183. //-----------------------------------------------------------------------------
  184. int C_PhysPropClientside::ParsePropData( void )
  185. {
  186. KeyValues *modelKeyValues = new KeyValues("");
  187. if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
  188. {
  189. modelKeyValues->deleteThis();
  190. return PARSE_FAILED_NO_DATA;
  191. }
  192. // Do we have a props section?
  193. KeyValues *pkvPropData = modelKeyValues->FindKey("prop_data");
  194. if ( !pkvPropData )
  195. {
  196. modelKeyValues->deleteThis();
  197. return PARSE_FAILED_NO_DATA;
  198. }
  199. int iResult = g_PropDataSystem.ParsePropFromKV( this, pkvPropData, modelKeyValues );
  200. modelKeyValues->deleteThis();
  201. return iResult;
  202. }
  203. bool C_PhysPropClientside::Initialize()
  204. {
  205. if ( InitializeAsClientEntity( STRING(GetModelName()), RENDER_GROUP_OPAQUE_ENTITY ) == false )
  206. {
  207. return false;
  208. }
  209. const model_t *mod = GetModel();
  210. if ( mod )
  211. {
  212. Vector mins, maxs;
  213. modelinfo->GetModelBounds( mod, mins, maxs );
  214. SetCollisionBounds( mins, maxs );
  215. }
  216. solid_t tmpSolid;
  217. // Create the object in the physics system
  218. if ( !PhysModelParseSolid( tmpSolid, this, GetModelIndex() ) )
  219. {
  220. DevMsg("C_PhysPropClientside::Initialize: PhysModelParseSolid failed for entity %i.\n", GetModelIndex() );
  221. return false;
  222. }
  223. else
  224. {
  225. m_pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, 0, m_spawnflags & SF_PHYSPROP_START_ASLEEP, &tmpSolid );
  226. if ( !m_pPhysicsObject )
  227. {
  228. // failed to create a physics object
  229. DevMsg(" C_PhysPropClientside::Initialize: VPhysicsInitNormal() failed for %s.\n", STRING(GetModelName()) );
  230. return false;
  231. }
  232. }
  233. // We want touch calls when we hit the world
  234. unsigned int flags = VPhysicsGetObject()->GetCallbackFlags();
  235. VPhysicsGetObject()->SetCallbackFlags( flags | CALLBACK_GLOBAL_TOUCH_STATIC );
  236. if ( m_spawnflags & SF_PHYSPROP_MOTIONDISABLED )
  237. {
  238. m_pPhysicsObject->EnableMotion( false );
  239. }
  240. Spawn(); // loads breakable & prop data
  241. if ( m_iPhysicsMode == PHYSICS_MULTIPLAYER_AUTODETECT )
  242. {
  243. m_iPhysicsMode = GetAutoMultiplayerPhysicsMode(
  244. CollisionProp()->OBBSize(), m_pPhysicsObject->GetMass() );
  245. }
  246. if ( m_spawnflags & SF_PHYSPROP_FORCE_SERVER_SIDE )
  247. {
  248. // forced to be server-side by map maker
  249. return false;
  250. }
  251. if ( m_iPhysicsMode != PHYSICS_MULTIPLAYER_CLIENTSIDE )
  252. {
  253. // spawn only clientside entities
  254. return false;
  255. }
  256. else
  257. {
  258. if ( engine->IsInEditMode() )
  259. {
  260. // don't spawn in map edit mode
  261. return false;
  262. }
  263. }
  264. if ( m_fadeMinDist < 0 )
  265. {
  266. // start fading out at 75% of r_propsmaxdist
  267. m_fadeMaxDist = r_propsmaxdist.GetFloat();
  268. m_fadeMinDist = r_propsmaxdist.GetFloat() * 0.75f;
  269. }
  270. // player can push it away
  271. SetCollisionGroup( COLLISION_GROUP_PUSHAWAY );
  272. UpdatePartitionListEntry();
  273. CollisionProp()->UpdatePartition();
  274. SetBlocksLOS( false ); // this should be a small object
  275. // Set up shadows; do it here so that objects can change shadowcasting state
  276. CreateShadow();
  277. UpdateVisibility();
  278. SetNextClientThink( CLIENT_THINK_NEVER );
  279. return true;
  280. }
  281. void C_PhysPropClientside::Spawn()
  282. {
  283. // Initialize damage modifiers. Must be done before baseclass spawn.
  284. m_flDmgModBullet = 1.0;
  285. m_flDmgModClub = 1.0;
  286. m_flDmgModExplosive = 1.0;
  287. BaseClass::Spawn();
  288. // we don't really precache models here, just checking how many we have:
  289. m_iNumBreakableChunks = PropBreakablePrecacheAll( GetModelIndex() );
  290. ParsePropData();
  291. // If we have no custom breakable chunks, see if we're breaking into generic ones
  292. if ( !m_iNumBreakableChunks )
  293. {
  294. if ( GetBreakableModel() != NULL_STRING && GetBreakableCount() )
  295. {
  296. m_iNumBreakableChunks = GetBreakableCount();
  297. }
  298. }
  299. // Setup takedamage based upon the health we parsed earlier
  300. if ( m_iHealth == 0 )
  301. {
  302. m_takedamage = DAMAGE_NO;
  303. }
  304. else
  305. {
  306. m_takedamage = DAMAGE_YES;
  307. }
  308. }
  309. void C_PhysPropClientside::OnTakeDamage( int iDamage ) // very simple version
  310. {
  311. if ( m_takedamage == DAMAGE_NO )
  312. return;
  313. m_iHealth -= iDamage;
  314. if (m_iHealth <= 0)
  315. {
  316. Break();
  317. }
  318. }
  319. float C_PhysPropClientside::GetMass()
  320. {
  321. if ( VPhysicsGetObject() )
  322. {
  323. return VPhysicsGetObject()->GetMass();
  324. }
  325. return 0.0f;
  326. }
  327. bool C_PhysPropClientside::IsAsleep()
  328. {
  329. if ( VPhysicsGetObject() )
  330. {
  331. return VPhysicsGetObject()->IsAsleep();
  332. }
  333. return true;
  334. }
  335. //-----------------------------------------------------------------------------
  336. // Purpose:
  337. //-----------------------------------------------------------------------------
  338. void C_PhysPropClientside::ClientThink( void )
  339. {
  340. if ( m_fDeathTime < 0 )
  341. {
  342. SetNextClientThink( CLIENT_THINK_NEVER );
  343. return;
  344. }
  345. if ( m_fDeathTime <= gpGlobals->curtime )
  346. {
  347. Release(); // Die
  348. return;
  349. }
  350. // fade out
  351. float alpha = (m_fDeathTime - gpGlobals->curtime)/FADEOUT_TIME;
  352. SetRenderMode( kRenderTransTexture );
  353. SetRenderColorA( alpha * 256 );
  354. SetNextClientThink( CLIENT_THINK_ALWAYS );
  355. }
  356. void C_PhysPropClientside::StartFadeOut( float fDelay )
  357. {
  358. m_fDeathTime = gpGlobals->curtime + fDelay + FADEOUT_TIME;
  359. SetNextClientThink( gpGlobals->curtime + fDelay );
  360. }
  361. void C_PhysPropClientside::Break()
  362. {
  363. m_takedamage = DAMAGE_NO;
  364. IPhysicsObject *pPhysics = VPhysicsGetObject();
  365. Vector velocity;
  366. AngularImpulse angVelocity;
  367. Vector origin;
  368. QAngle angles;
  369. AddSolidFlags( FSOLID_NOT_SOLID );
  370. if ( pPhysics )
  371. {
  372. pPhysics->GetVelocity( &velocity, &angVelocity );
  373. pPhysics->GetPosition( &origin, &angles );
  374. pPhysics->RecheckCollisionFilter();
  375. }
  376. else
  377. {
  378. velocity = GetAbsVelocity();
  379. QAngleToAngularImpulse( GetLocalAngularVelocity(), angVelocity );
  380. origin = GetAbsOrigin();
  381. angles = GetAbsAngles();
  382. }
  383. breakablepropparams_t params( origin, angles, velocity, angVelocity );
  384. params.impactEnergyScale = m_impactEnergyScale;
  385. params.defCollisionGroup = GetCollisionGroup();
  386. if ( params.defCollisionGroup == COLLISION_GROUP_NONE )
  387. {
  388. // don't automatically make anything COLLISION_GROUP_NONE or it will
  389. // collide with debris being ejected by breaking
  390. params.defCollisionGroup = COLLISION_GROUP_INTERACTIVE;
  391. }
  392. // no damage/damage force? set a burst of 100 for some movement
  393. params.defBurstScale = 100;
  394. // spwan break chunks
  395. PropBreakableCreateAll( GetModelIndex(), pPhysics, params, this, -1, false );
  396. Release(); // destroy object
  397. }
  398. void C_PhysPropClientside::Clone( Vector &velocity )
  399. {
  400. C_PhysPropClientside *pEntity = C_PhysPropClientside::CreateNew();
  401. if ( !pEntity )
  402. return;
  403. pEntity->m_spawnflags = m_spawnflags;
  404. // We never want to be motion disabled
  405. pEntity->m_spawnflags &= ~SF_PHYSPROP_MOTIONDISABLED;
  406. pEntity->SetDmgModBullet( GetDmgModBullet() );
  407. pEntity->SetDmgModClub( GetDmgModClub() );
  408. pEntity->SetDmgModExplosive( GetDmgModExplosive() );
  409. pEntity->SetModelName( GetModelName() );
  410. pEntity->SetLocalOrigin( GetLocalOrigin() );
  411. pEntity->SetLocalAngles( GetLocalAngles() );
  412. pEntity->SetOwnerEntity( this );
  413. pEntity->SetPhysicsMode( PHYSICS_MULTIPLAYER_CLIENTSIDE );
  414. if ( !pEntity->Initialize() )
  415. {
  416. pEntity->Release();
  417. return;
  418. }
  419. pEntity->m_nSkin = m_nSkin;
  420. pEntity->m_iHealth = m_iHealth;
  421. if ( pEntity->m_iHealth == 0 )
  422. {
  423. // if no health, don't collide with player anymore, don't take damage
  424. pEntity->m_takedamage = DAMAGE_NO;
  425. pEntity->SetCollisionGroup( COLLISION_GROUP_NONE );
  426. }
  427. IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
  428. if( pPhysicsObject )
  429. {
  430. // randomize velocity by 5%
  431. float rndf = RandomFloat( -0.025, 0.025 );
  432. Vector rndVel = velocity + rndf*velocity;
  433. pPhysicsObject->AddVelocity( &rndVel, NULL );
  434. }
  435. else
  436. {
  437. // failed to create a physics object
  438. pEntity->Release();
  439. }
  440. }
  441. void C_PhysPropClientside::ImpactTrace( trace_t *pTrace, int iDamageType, const char *pCustomImpactName )
  442. {
  443. VPROF( "C_PhysPropClientside::ImpactTrace" );
  444. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  445. if( !pPhysicsObject )
  446. return;
  447. Vector dir = pTrace->endpos - pTrace->startpos;
  448. int iDamage = 0;
  449. if ( iDamageType == DMG_BLAST )
  450. {
  451. iDamage = VectorLength( dir );
  452. dir *= 500; // adjust impact strenght
  453. // apply force at object mass center
  454. pPhysicsObject->ApplyForceCenter( dir );
  455. }
  456. else
  457. {
  458. Vector hitpos;
  459. VectorMA( pTrace->startpos, pTrace->fraction, dir, hitpos );
  460. VectorNormalize( dir );
  461. // guess avg damage
  462. if ( iDamageType == DMG_BULLET )
  463. {
  464. iDamage = 30;
  465. }
  466. else
  467. {
  468. iDamage = 50;
  469. }
  470. dir *= 4000; // adjust impact strenght
  471. // apply force where we hit it
  472. pPhysicsObject->ApplyForceOffset( dir, hitpos );
  473. // Build the impact data
  474. CEffectData data;
  475. data.m_vOrigin = pTrace->endpos;
  476. data.m_vStart = pTrace->startpos;
  477. data.m_nSurfaceProp = pTrace->surface.surfaceProps;
  478. data.m_nDamageType = iDamageType;
  479. data.m_nHitBox = pTrace->hitbox;
  480. data.m_hEntity = GetRefEHandle();
  481. // Send it on its way
  482. if ( !pCustomImpactName )
  483. {
  484. DispatchEffect( "Impact", data );
  485. }
  486. else
  487. {
  488. DispatchEffect( pCustomImpactName, data );
  489. }
  490. }
  491. // Clone( dir ); // debug code
  492. OnTakeDamage( iDamage );
  493. }
  494. const char *C_PhysPropClientside::ParseEntity( const char *pEntData )
  495. {
  496. CEntityMapData entData( (char*)pEntData );
  497. char className[MAPKEY_MAXLENGTH];
  498. MDLCACHE_CRITICAL_SECTION();
  499. if (!entData.ExtractValue("classname", className))
  500. {
  501. Error( "classname missing from entity!\n" );
  502. }
  503. if ( !Q_strcmp( className, "prop_physics_multiplayer" ) )
  504. {
  505. // always force clientside entitis placed in maps
  506. C_PhysPropClientside *pEntity = C_PhysPropClientside::CreateNew( true );
  507. if ( pEntity )
  508. { // Set up keyvalues.
  509. pEntity->ParseMapData(&entData);
  510. if ( !pEntity->Initialize() )
  511. pEntity->Release();
  512. return entData.CurrentBufferPosition();
  513. }
  514. }
  515. if ( !Q_strcmp( className, "func_proprrespawnzone" ) )
  516. {
  517. C_FuncPhysicsRespawnZone *pEntity = new C_FuncPhysicsRespawnZone();
  518. if ( pEntity )
  519. {
  520. // Set up keyvalues.
  521. pEntity->ParseMapData(&entData);
  522. if ( !pEntity->Initialize() )
  523. pEntity->Release();
  524. return entData.CurrentBufferPosition();
  525. }
  526. }
  527. // Just skip past all the keys.
  528. char keyName[MAPKEY_MAXLENGTH];
  529. char value[MAPKEY_MAXLENGTH];
  530. if ( entData.GetFirstKey(keyName, value) )
  531. {
  532. do
  533. {
  534. }
  535. while ( entData.GetNextKey(keyName, value) );
  536. }
  537. //
  538. // Return the current parser position in the data block
  539. //
  540. return entData.CurrentBufferPosition();
  541. }
  542. //-----------------------------------------------------------------------------
  543. // Purpose: Only called on BSP load. Parses and spawns all the entities in the BSP.
  544. // Input : pMapData - Pointer to the entity data block to parse.
  545. //-----------------------------------------------------------------------------
  546. void C_PhysPropClientside::ParseAllEntities(const char *pMapData)
  547. {
  548. int nEntities = 0;
  549. char szTokenBuffer[MAPKEY_MAXLENGTH];
  550. //
  551. // Loop through all entities in the map data, creating each.
  552. //
  553. for ( ; true; pMapData = MapEntity_SkipToNextEntity(pMapData, szTokenBuffer) )
  554. {
  555. //
  556. // Parse the opening brace.
  557. //
  558. char token[MAPKEY_MAXLENGTH];
  559. pMapData = MapEntity_ParseToken( pMapData, token );
  560. //
  561. // Check to see if we've finished or not.
  562. //
  563. if (!pMapData)
  564. break;
  565. if (token[0] != '{')
  566. {
  567. Error( "MapEntity_ParseAllEntities: found %s when expecting {", token);
  568. continue;
  569. }
  570. //
  571. // Parse the entity and add it to the spawn list.
  572. //
  573. pMapData = ParseEntity( pMapData );
  574. nEntities++;
  575. }
  576. }
  577. CBaseEntity *BreakModelCreateSingle( CBaseEntity *pOwner, breakmodel_t *pModel, const Vector &position,
  578. const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, int nSkin, const breakablepropparams_t &params )
  579. {
  580. C_PhysPropClientside *pEntity = C_PhysPropClientside::CreateNew();
  581. if ( !pEntity )
  582. return NULL;
  583. // UNDONE: Allow .qc to override spawnflags for child pieces
  584. C_PhysPropClientside *pBreakableOwner = dynamic_cast<C_PhysPropClientside *>(pOwner);
  585. // Inherit the base object's damage modifiers
  586. if ( pBreakableOwner )
  587. {
  588. pEntity->SetEffects( pBreakableOwner->GetEffects() );
  589. pEntity->m_spawnflags = pBreakableOwner->m_spawnflags;
  590. // We never want to be motion disabled
  591. pEntity->m_spawnflags &= ~SF_PHYSPROP_MOTIONDISABLED;
  592. pEntity->SetDmgModBullet( pBreakableOwner->GetDmgModBullet() );
  593. pEntity->SetDmgModClub( pBreakableOwner->GetDmgModClub() );
  594. pEntity->SetDmgModExplosive( pBreakableOwner->GetDmgModExplosive() );
  595. // FIXME: If this was created from a client-side entity which was in the
  596. // middle of ramping the fade scale, we're screwed.
  597. pEntity->CopyFadeFrom( pBreakableOwner );
  598. }
  599. pEntity->SetModelName( AllocPooledString( pModel->modelName ) );
  600. pEntity->SetLocalOrigin( position );
  601. pEntity->SetLocalAngles( angles );
  602. pEntity->SetOwnerEntity( pOwner );
  603. pEntity->SetPhysicsMode( PHYSICS_MULTIPLAYER_CLIENTSIDE );
  604. if ( !pEntity->Initialize() )
  605. {
  606. pEntity->Release();
  607. return NULL;
  608. }
  609. pEntity->m_nSkin = nSkin;
  610. pEntity->m_iHealth = pModel->health;
  611. #ifdef TF_CLIENT_DLL
  612. pEntity->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  613. #endif
  614. #ifdef DOD_DLL
  615. pEntity->SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  616. #endif
  617. if ( pModel->health == 0 )
  618. {
  619. // if no health, don't collide with player anymore, don't take damage
  620. pEntity->m_takedamage = DAMAGE_NO;
  621. if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_PUSHAWAY )
  622. {
  623. pEntity->SetCollisionGroup( COLLISION_GROUP_NONE );
  624. }
  625. }
  626. if ( pModel->fadeTime > 0 )
  627. {
  628. pEntity->StartFadeOut( pModel->fadeTime );
  629. }
  630. if ( pModel->fadeMinDist > 0 && pModel->fadeMaxDist >= pModel->fadeMinDist )
  631. {
  632. pEntity->SetFadeMinMax( pModel->fadeMinDist, pModel->fadeMaxDist );
  633. }
  634. if ( pModel->isRagdoll )
  635. {
  636. DevMsg( "BreakModelCreateSingle: clientside doesn't support ragdoll breakmodels.\n" );
  637. }
  638. IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
  639. if( pPhysicsObject )
  640. {
  641. // randomize velocity by 5%
  642. float rndf = RandomFloat( -0.025, 0.025 );
  643. Vector rndVel = velocity + rndf*velocity;
  644. pPhysicsObject->AddVelocity( &rndVel, &angVelocity );
  645. }
  646. else
  647. {
  648. // failed to create a physics object
  649. pEntity->Release();
  650. return NULL;
  651. }
  652. return pEntity;
  653. }
  654. //======================================================================================================================
  655. // PROP RESPAWN ZONES
  656. //======================================================================================================================
  657. C_FuncPhysicsRespawnZone::C_FuncPhysicsRespawnZone( void )
  658. {
  659. s_RespawnZoneList.AddToTail( this );
  660. }
  661. C_FuncPhysicsRespawnZone::~C_FuncPhysicsRespawnZone( void )
  662. {
  663. s_RespawnZoneList.FindAndRemove( this );
  664. }
  665. //-----------------------------------------------------------------------------
  666. // Purpose:
  667. //-----------------------------------------------------------------------------
  668. bool C_FuncPhysicsRespawnZone::KeyValue( const char *szKeyName, const char *szValue )
  669. {
  670. if (FStrEq(szKeyName, "model"))
  671. {
  672. SetModelName( AllocPooledString( szValue ) );
  673. }
  674. else
  675. {
  676. if ( !BaseClass::KeyValue( szKeyName, szValue ) )
  677. {
  678. // key hasn't been handled
  679. return false;
  680. }
  681. }
  682. return true;
  683. }
  684. //-----------------------------------------------------------------------------
  685. // Purpose:
  686. //-----------------------------------------------------------------------------
  687. bool C_FuncPhysicsRespawnZone::Initialize( void )
  688. {
  689. if ( InitializeAsClientEntity( STRING(GetModelName()), RENDER_GROUP_OPAQUE_ENTITY ) == false )
  690. return false;
  691. SetSolid( SOLID_BSP );
  692. AddSolidFlags( FSOLID_NOT_SOLID );
  693. AddSolidFlags( FSOLID_TRIGGER );
  694. SetMoveType( MOVETYPE_NONE );
  695. const model_t *mod = GetModel();
  696. if ( mod )
  697. {
  698. Vector mins, maxs;
  699. modelinfo->GetModelBounds( mod, mins, maxs );
  700. SetCollisionBounds( mins, maxs );
  701. }
  702. Spawn();
  703. AddEffects( EF_NODRAW );
  704. UpdatePartitionListEntry();
  705. CollisionProp()->UpdatePartition();
  706. UpdateVisibility();
  707. SetNextClientThink( gpGlobals->curtime + (cl_phys_props_respawnrate.GetFloat() * RandomFloat(1.0,1.1)) );
  708. return true;
  709. }
  710. //-----------------------------------------------------------------------------
  711. // Purpose: Iterate over all prop respawn zones and find the props inside them
  712. //-----------------------------------------------------------------------------
  713. void C_PhysPropClientside::InitializePropRespawnZones(void)
  714. {
  715. for ( int i = 0; i < s_RespawnZoneList.Count(); i++ )
  716. {
  717. C_FuncPhysicsRespawnZone *pZone = s_RespawnZoneList[i];
  718. pZone->InitializePropsWithin();
  719. }
  720. }
  721. //-----------------------------------------------------------------------------
  722. // Purpose:
  723. //-----------------------------------------------------------------------------
  724. void C_FuncPhysicsRespawnZone::InitializePropsWithin( void )
  725. {
  726. // Find the props inside this zone
  727. for ( int i = 0; i < s_PhysPropList.Count(); i++ )
  728. {
  729. C_PhysPropClientside *pProp = s_PhysPropList[i];
  730. if ( CollisionProp()->IsPointInBounds( pProp->WorldSpaceCenter() ) )
  731. {
  732. pProp->SetRespawnZone( this );
  733. // This is a crappy way to do this
  734. int iProp = m_PropList.AddToTail();
  735. m_PropList[iProp].iszModelName = pProp->GetModelName();
  736. m_PropList[iProp].vecOrigin = pProp->GetAbsOrigin();
  737. m_PropList[iProp].vecAngles = pProp->GetAbsAngles();
  738. m_PropList[iProp].iSkin = pProp->m_nSkin;
  739. m_PropList[iProp].iHealth = pProp->m_iHealth;
  740. m_PropList[iProp].iSpawnFlags = pProp->m_spawnflags;
  741. m_PropList[iProp].hClientEntity = pProp->GetClientHandle();
  742. }
  743. }
  744. }
  745. //-----------------------------------------------------------------------------
  746. // Purpose:
  747. //-----------------------------------------------------------------------------
  748. void C_FuncPhysicsRespawnZone::PropDestroyed( C_PhysPropClientside *pProp )
  749. {
  750. for ( int i = 0; i < m_PropList.Count(); i++ )
  751. {
  752. if ( pProp->GetClientHandle() == m_PropList[i].hClientEntity )
  753. {
  754. m_PropList[i].hClientEntity = INVALID_CLIENTENTITY_HANDLE;
  755. return;
  756. }
  757. }
  758. // We've got a clientside prop that thinks it belongs to a zone that doesn't recognise it. Shouldn't happen.
  759. Assert(0);
  760. }
  761. //-----------------------------------------------------------------------------
  762. // Purpose:
  763. //-----------------------------------------------------------------------------
  764. bool C_FuncPhysicsRespawnZone::CanMovePropAt( Vector vecOrigin, const Vector &vecMins, const Vector &vecMaxs )
  765. {
  766. float flDist = cl_phys_props_respawndist.GetFloat();
  767. // Do a distance check first. We don't want to move props when the player is near 'em.
  768. if ( (MainViewOrigin() - vecOrigin).LengthSqr() < (flDist*flDist) )
  769. return false;
  770. // Now make sure it's not in view
  771. if( engine->IsBoxInViewCluster( vecMins + vecOrigin, vecMaxs + vecOrigin) )
  772. return false;
  773. if( !engine->CullBox( vecMins + vecOrigin, vecMaxs + vecOrigin ) )
  774. return false;
  775. return true;
  776. }
  777. //-----------------------------------------------------------------------------
  778. // Purpose:
  779. //-----------------------------------------------------------------------------
  780. void C_FuncPhysicsRespawnZone::RespawnProps( void )
  781. {
  782. for ( int i = 0; i < m_PropList.Count(); i++ )
  783. {
  784. if ( m_PropList[i].hClientEntity == INVALID_CLIENTENTITY_HANDLE )
  785. {
  786. if ( !CanMovePropAt( m_PropList[i].vecOrigin, -Vector(32,32,32), Vector(32,32,32) ) )
  787. continue;
  788. // This is a crappy way to do this
  789. C_PhysPropClientside *pEntity = C_PhysPropClientside::CreateNew();
  790. if ( pEntity )
  791. {
  792. pEntity->m_spawnflags = m_PropList[i].iSpawnFlags;
  793. pEntity->SetModelName( m_PropList[i].iszModelName );
  794. pEntity->SetAbsOrigin( m_PropList[i].vecOrigin );
  795. pEntity->SetAbsAngles( m_PropList[i].vecAngles );
  796. pEntity->SetPhysicsMode( PHYSICS_MULTIPLAYER_CLIENTSIDE );
  797. pEntity->m_nSkin = m_PropList[i].iSkin;
  798. pEntity->m_iHealth = m_PropList[i].iHealth;
  799. if ( pEntity->m_iHealth == 0 )
  800. {
  801. pEntity->m_takedamage = DAMAGE_NO;
  802. }
  803. if ( !pEntity->Initialize() )
  804. {
  805. pEntity->Release();
  806. }
  807. else
  808. {
  809. pEntity->SetRespawnZone( this );
  810. m_PropList[i].hClientEntity = pEntity->GetClientHandle();
  811. }
  812. }
  813. }
  814. else
  815. {
  816. // If the prop has moved, bring it back
  817. C_BaseEntity *pEntity = ClientEntityList().GetBaseEntityFromHandle( m_PropList[i].hClientEntity );
  818. if ( pEntity )
  819. {
  820. if ( !CollisionProp()->IsPointInBounds( pEntity->WorldSpaceCenter() ) )
  821. {
  822. Vector vecMins, vecMaxs;
  823. pEntity->CollisionProp()->WorldSpaceSurroundingBounds( &vecMins, &vecMaxs );
  824. if ( !CanMovePropAt( m_PropList[i].vecOrigin, vecMins, vecMaxs ) ||
  825. !CanMovePropAt( pEntity->GetAbsOrigin(), vecMins, vecMaxs ) )
  826. continue;
  827. pEntity->SetAbsOrigin( m_PropList[i].vecOrigin );
  828. pEntity->SetAbsAngles( m_PropList[i].vecAngles );
  829. IPhysicsObject *pPhys = pEntity->VPhysicsGetObject();
  830. if ( pPhys )
  831. {
  832. pPhys->SetPosition( pEntity->GetAbsOrigin(), pEntity->GetAbsAngles(), true );
  833. }
  834. }
  835. }
  836. }
  837. }
  838. }
  839. //-----------------------------------------------------------------------------
  840. // Purpose:
  841. //-----------------------------------------------------------------------------
  842. void C_FuncPhysicsRespawnZone::ClientThink( void )
  843. {
  844. RespawnProps();
  845. SetNextClientThink( gpGlobals->curtime + (cl_phys_props_respawnrate.GetFloat() * RandomFloat(1.0,1.1)) );
  846. }