Counter Strike : Global Offensive Source Code
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.

7484 lines
223 KiB

  1. //===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose: static_prop - don't move, don't animate, don't do anything.
  4. // physics_prop - move, take damage, but don't animate
  5. //
  6. //===========================================================================//
  7. #include "cbase.h"
  8. #include "ai_basenpc.h"
  9. #include "npcevent.h"
  10. #include "engine/IEngineSound.h"
  11. #include "locksounds.h"
  12. #include "filters.h"
  13. #include "physics.h"
  14. #include "vphysics_interface.h"
  15. #include "vphysics/friction.h"
  16. #include "entityoutput.h"
  17. #include "vcollide_parse.h"
  18. #include "studio.h"
  19. #include "explode.h"
  20. #include "utlrbtree.h"
  21. #include "tier1/strtools.h"
  22. #include "physics_impact_damage.h"
  23. #include "keyvalues.h"
  24. #include "filesystem.h"
  25. #include "scriptevent.h"
  26. #include "entityblocker.h"
  27. #include "soundent.h"
  28. #include "EntityFlame.h"
  29. #include "game.h"
  30. #include "physics_prop_ragdoll.h"
  31. #include "decals.h"
  32. #include "hierarchy.h"
  33. #include "shareddefs.h"
  34. #include "physobj.h"
  35. #include "physics_npc_solver.h"
  36. #include "SoundEmitterSystem/isoundemittersystembase.h"
  37. #include "datacache/imdlcache.h"
  38. #include "doors.h"
  39. #include "physics_collisionevent.h"
  40. #include "GameStats.h"
  41. #include "vehicle_base.h"
  42. #include "tier0/icommandline.h"
  43. #include "pushentity.h"
  44. #include "cvisibilitymonitor.h"
  45. #include "usermessages.h"
  46. #include "particle_parse.h"
  47. #include "collisionutils.h"
  48. #include "BasePropDoor.h"
  49. #include "phys_controller.h"
  50. #ifdef PORTAL2
  51. #include "portal_base2d_shared.h"
  52. #include "portal_grabcontroller_shared.h"
  53. #endif // PORTAL2
  54. #include "vstdlib/ikeyvaluessystem.h"
  55. // memdbgon must be the last include file in a .cpp file!!!
  56. #include "tier0/memdbgon.h"
  57. #define DOOR_HARDWARE_GROUP 1
  58. // Any barrel farther away than this is ignited rather than exploded.
  59. #define PROP_EXPLOSION_IGNITE_RADIUS 32.0f
  60. // How many times to remind the player that supply crates can be broken
  61. // (displayed when the supply crate is picked up)
  62. #define NUM_SUPPLY_CRATE_HUD_HINTS 3
  63. ConVar g_debug_doors( "g_debug_doors", "0" );
  64. ConVar breakable_disable_gib_limit( "breakable_disable_gib_limit", "0" );
  65. ConVar breakable_multiplayer( "breakable_multiplayer", "1" );
  66. // AI Interaction for being hit by a physics object
  67. int g_interactionHitByPlayerThrownPhysObj = 0;
  68. int g_interactionPlayerPuntedHeavyObject = 0;
  69. int g_ActiveGibCount = 0;
  70. ConVar prop_active_gib_limit( "prop_active_gib_limit", IsGameConsole() ? "32" : "64" );
  71. ConVar prop_active_gib_max_fade_time( "prop_active_gib_max_fade_time", "12" );
  72. #define ACTIVE_GIB_LIMIT prop_active_gib_limit.GetInt()
  73. #define ACTIVE_GIB_FADE prop_active_gib_max_fade_time.GetInt()
  74. // Damage type modifiers for breakable objects.
  75. ConVar func_breakdmg_bullet( "func_breakdmg_bullet", "0.5" );
  76. ConVar func_breakdmg_club( "func_breakdmg_club", "1.5" );
  77. ConVar func_breakdmg_explosive( "func_breakdmg_explosive", "1.25" );
  78. ConVar sv_turbophysics( "sv_turbophysics", "0", FCVAR_REPLICATED, "Turns on turbo physics" );
  79. #ifdef PORTAL2
  80. ConVar sv_props_funnel_into_portals( "sv_props_funnel_into_portals", "1", FCVAR_CHEAT );
  81. ConVar sv_props_funnel_into_portals_deceleration( "sv_props_funnel_into_portals_deceleration", "2.0f", FCVAR_CHEAT, "When a funneling prop is leaving a portal, decelerate any velocity that is in opposition to funneling by this amount per second" );
  82. ConVar prop_break_disable_float( "prop_break_disable_float", "1" );
  83. #else
  84. ConVar prop_break_disable_float( "prop_break_disable_float", "0" );
  85. #endif // PORTAL2
  86. #ifdef HL2_EPISODIC
  87. #define PROP_FLARE_LIFETIME 30.0f
  88. #define PROP_FLARE_IGNITE_SUBSTRACT 5.0f
  89. CBaseEntity *CreateFlare( Vector vOrigin, QAngle Angles, CBaseEntity *pOwner, float flDuration );
  90. void KillFlare( CBaseEntity *pOwnerEntity, CBaseEntity *pEntity, float flKillTime );
  91. #endif
  92. //-----------------------------------------------------------------------------
  93. // Purpose: Breakable objects take different levels of damage based upon the damage type.
  94. // This isn't contained by CBaseProp, because func_breakables use it as well.
  95. //-----------------------------------------------------------------------------
  96. float GetBreakableDamage( const CTakeDamageInfo &inputInfo, IBreakableWithPropData *pProp )
  97. {
  98. float flDamage = inputInfo.GetDamage();
  99. int iDmgType = inputInfo.GetDamageType();
  100. // Bullet damage?
  101. if ( iDmgType & DMG_BULLET )
  102. {
  103. // Buckshot does double damage to breakables
  104. if ( iDmgType & DMG_BUCKSHOT )
  105. {
  106. if ( pProp )
  107. {
  108. flDamage *= (pProp->GetDmgModBullet() * 2);
  109. }
  110. else
  111. {
  112. // Bullets do little damage to breakables
  113. flDamage *= (func_breakdmg_bullet.GetFloat() * 2);
  114. }
  115. }
  116. else
  117. {
  118. if ( pProp )
  119. {
  120. flDamage *= pProp->GetDmgModBullet();
  121. }
  122. else
  123. {
  124. // Bullets do little damage to breakables
  125. flDamage *= func_breakdmg_bullet.GetFloat();
  126. }
  127. }
  128. }
  129. // Club damage?
  130. if ( iDmgType & DMG_CLUB )
  131. {
  132. if ( pProp )
  133. {
  134. flDamage *= pProp->GetDmgModClub();
  135. }
  136. else
  137. {
  138. // Club does extra damage
  139. flDamage *= func_breakdmg_club.GetFloat();
  140. }
  141. }
  142. // Explosive damage?
  143. if ( iDmgType & DMG_BLAST )
  144. {
  145. if ( pProp )
  146. {
  147. flDamage *= pProp->GetDmgModExplosive();
  148. }
  149. else
  150. {
  151. // Explosions do extra damage
  152. flDamage *= func_breakdmg_explosive.GetFloat();
  153. }
  154. }
  155. // Fire damage?
  156. if ( iDmgType & DMG_BURN )
  157. {
  158. if ( pProp )
  159. {
  160. flDamage *= pProp->GetDmgModFire();
  161. }
  162. }
  163. if ( (iDmgType & DMG_SLASH) && (iDmgType & DMG_CRUSH) )
  164. {
  165. // Cut by a Ravenholm propeller trap
  166. flDamage *= 10.0f;
  167. }
  168. // Poison & other timebased damage types do no damage
  169. if ( g_pGameRules->Damage_IsTimeBased( iDmgType ) )
  170. {
  171. flDamage = 0;
  172. }
  173. return flDamage;
  174. }
  175. //=============================================================================================================
  176. // BASE PROP
  177. //=============================================================================================================
  178. //-----------------------------------------------------------------------------
  179. // Purpose:
  180. //-----------------------------------------------------------------------------
  181. void CBaseProp::Spawn( void )
  182. {
  183. char *szModel = (char *)STRING( GetModelName() );
  184. if (!szModel || !*szModel)
  185. {
  186. Warning( "prop %s at %.0f %.0f %0.f missing modelname\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
  187. UTIL_Remove( this );
  188. return;
  189. }
  190. PrecacheModel( szModel );
  191. Precache();
  192. SetModel( szModel );
  193. // Load this prop's data from the propdata file
  194. int iResult = ParsePropData();
  195. if ( !OverridePropdata() )
  196. {
  197. if ( iResult == PARSE_FAILED_BAD_DATA )
  198. {
  199. DevWarning( "%s at %.0f %.0f %0.f uses model %s, which has an invalid prop_data type. DELETED.\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, szModel );
  200. UTIL_Remove( this );
  201. return;
  202. }
  203. else if ( iResult == PARSE_FAILED_NO_DATA )
  204. {
  205. // If we don't have data, but we're a prop_physics, fail
  206. if ( FClassnameIs( this, "prop_physics" ) )
  207. {
  208. DevWarning( "%s at %.0f %.0f %0.f uses model %s, which has no propdata which means it must be used on a prop_static. DELETED.\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, szModel );
  209. UTIL_Remove( this );
  210. return;
  211. }
  212. }
  213. else if ( iResult == PARSE_SUCCEEDED )
  214. {
  215. // If we have data, and we're not a physics prop, fail
  216. if ( !dynamic_cast<CPhysicsProp*>(this) )
  217. {
  218. DevWarning( "%s at %.0f %.0f %0.f uses model %s, which has propdata which means that it be used on a prop_physics. DELETED.\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z, szModel );
  219. UTIL_Remove( this );
  220. return;
  221. }
  222. }
  223. }
  224. SetNextThink( TICK_NEVER_THINK );
  225. SetMoveType( MOVETYPE_PUSH );
  226. m_takedamage = DAMAGE_NO;
  227. m_flAnimTime = gpGlobals->curtime;
  228. m_flPlaybackRate = 0.0;
  229. SetCycle( 0 );
  230. }
  231. //-----------------------------------------------------------------------------
  232. // Purpose:
  233. //-----------------------------------------------------------------------------
  234. void CBaseProp::Precache( void )
  235. {
  236. if ( GetModelName() == NULL_STRING )
  237. {
  238. Msg( "%s at (%.3f, %.3f, %.3f) has no model name!\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
  239. SetModelName( AllocPooledString( "models/error.mdl" ) );
  240. }
  241. PrecacheModel( STRING( GetModelName() ) );
  242. PrecacheScriptSound( "Metal.SawbladeStick" );
  243. PrecacheScriptSound( "PropaneTank.Burst" );
  244. #ifdef HL2_EPISODIC
  245. UTIL_PrecacheOther( "env_flare" );
  246. #endif
  247. BaseClass::Precache();
  248. }
  249. //-----------------------------------------------------------------------------
  250. // Purpose:
  251. //-----------------------------------------------------------------------------
  252. void CBaseProp::Activate( void )
  253. {
  254. BaseClass::Activate();
  255. // Make sure mapmakers haven't used the wrong prop type.
  256. if ( m_takedamage == DAMAGE_NO && m_iHealth != 0 )
  257. {
  258. Warning("%s has a health specified in model '%s'. Use prop_physics or prop_dynamic instead.\n", GetClassname(), STRING(GetModelName()) );
  259. }
  260. }
  261. //-----------------------------------------------------------------------------
  262. // Purpose: Handles keyvalues from the BSP. Called before spawning.
  263. //-----------------------------------------------------------------------------
  264. bool CBaseProp::KeyValue( const char *szKeyName, const char *szValue )
  265. {
  266. if ( FStrEq(szKeyName, "health") )
  267. {
  268. // Only override props are allowed to override health.
  269. if ( FClassnameIs( this, "prop_physics_override" ) || FClassnameIs( this, "prop_dynamic_override" ) )
  270. return BaseClass::KeyValue( szKeyName, szValue );
  271. return true;
  272. }
  273. else
  274. {
  275. return BaseClass::KeyValue( szKeyName, szValue );
  276. }
  277. return true;
  278. }
  279. //-----------------------------------------------------------------------------
  280. // Purpose: Calculate whether this prop should block LOS or not
  281. //-----------------------------------------------------------------------------
  282. void CBaseProp::CalculateBlockLOS( void )
  283. {
  284. // We block LOS if:
  285. // - One of our dimensions is >40
  286. // - Our other 2 dimensions are >30
  287. // By default, entities block LOS, so we only need to detect non-blockage
  288. bool bFoundLarge = false;
  289. Vector vecSize = CollisionProp()->OBBMaxs() - CollisionProp()->OBBMins();
  290. for ( int i = 0; i < 3; i++ )
  291. {
  292. if ( vecSize[i] > 40 )
  293. {
  294. bFoundLarge = true;
  295. }
  296. if ( vecSize[i] > 30 )
  297. continue;
  298. // Dimension smaller than 30.
  299. SetBlocksLOS( false );
  300. return;
  301. }
  302. if ( !bFoundLarge )
  303. {
  304. // No dimension larger than 40
  305. SetBlocksLOS( false );
  306. }
  307. }
  308. //-----------------------------------------------------------------------------
  309. // Purpose: Parse this prop's data from the model, if it has a keyvalues section.
  310. // Returns true only if this prop is using a model that has a prop_data section that's invalid.
  311. //-----------------------------------------------------------------------------
  312. int CBaseProp::ParsePropData( void )
  313. {
  314. KeyValues *pModelKV = modelinfo->GetModelKeyValues( GetModel() );
  315. if ( !pModelKV )
  316. return PARSE_FAILED_NO_DATA;
  317. static int keyPropData = KeyValuesSystem()->GetSymbolForString( "prop_data" );
  318. // Do we have a props section?
  319. KeyValues *pkvPropData = pModelKV->FindKey( keyPropData );
  320. if ( !pkvPropData )
  321. return PARSE_FAILED_NO_DATA;
  322. int iResult = g_PropDataSystem.ParsePropFromKV( this, dynamic_cast<IBreakableWithPropData *>(this), pkvPropData, pModelKV );
  323. return iResult;
  324. }
  325. //-----------------------------------------------------------------------------
  326. // Purpose:
  327. //-----------------------------------------------------------------------------
  328. void CBaseProp::DrawDebugGeometryOverlays( void )
  329. {
  330. BaseClass::DrawDebugGeometryOverlays();
  331. if ( m_debugOverlays & OVERLAY_PROP_DEBUG )
  332. {
  333. if ( m_takedamage == DAMAGE_NO )
  334. {
  335. NDebugOverlay::EntityBounds(this, 255, 0, 0, 0, 0 );
  336. }
  337. else if ( m_takedamage == DAMAGE_EVENTS_ONLY )
  338. {
  339. NDebugOverlay::EntityBounds(this, 255, 255, 255, 0, 0 );
  340. }
  341. else
  342. {
  343. // Remap health to green brightness
  344. float flG = RemapVal( m_iHealth, 0, 100, 64, 255 );
  345. flG = clamp( flG, 0, 255 );
  346. NDebugOverlay::EntityBounds(this, 0, flG, 0, 0, 0 );
  347. }
  348. }
  349. }
  350. class CEnableMotionFixup : public CBaseEntity
  351. {
  352. DECLARE_CLASS( CEnableMotionFixup, CBaseEntity );
  353. };
  354. LINK_ENTITY_TO_CLASS( point_enable_motion_fixup, CEnableMotionFixup );
  355. static const char *s_pFadeScaleThink = "FadeScaleThink";
  356. static const char *s_pPropAnimateThink = "PropAnimateThink";
  357. void CBreakableProp::SetEnableMotionPosition( const Vector &position, const QAngle &angles )
  358. {
  359. ClearEnableMotionPosition();
  360. CBaseEntity *pFixup = CBaseEntity::Create( "point_enable_motion_fixup", position, angles, this );
  361. if ( pFixup )
  362. {
  363. pFixup->SetParent( this );
  364. }
  365. }
  366. CBaseEntity *CBreakableProp::FindEnableMotionFixup()
  367. {
  368. CUtlVector<CBaseEntity*> list;
  369. GetAllChildren( this, list );
  370. for ( int i = list.Count()-1; i >= 0; --i )
  371. {
  372. if ( FClassnameIs( list[i], "point_enable_motion_fixup" ) )
  373. return list[i];
  374. }
  375. return NULL;
  376. }
  377. bool CBreakableProp::GetEnableMotionPosition( Vector *pPosition, QAngle *pAngles )
  378. {
  379. CBaseEntity *pFixup = FindEnableMotionFixup();
  380. if ( !pFixup )
  381. return false;
  382. *pPosition = pFixup->GetAbsOrigin();
  383. *pAngles = pFixup->GetAbsAngles();
  384. return true;
  385. }
  386. void CBreakableProp::ClearEnableMotionPosition()
  387. {
  388. CBaseEntity *pFixup = FindEnableMotionFixup();
  389. if ( pFixup )
  390. {
  391. UnlinkFromParent( pFixup );
  392. UTIL_Remove( pFixup );
  393. }
  394. }
  395. //-----------------------------------------------------------------------------
  396. //-----------------------------------------------------------------------------
  397. void CBreakableProp::Ignite( float flFlameLifetime, bool bNPCOnly, float flSize, bool bCalledByLevelDesigner )
  398. {
  399. if( IsOnFire() )
  400. return;
  401. if( !HasInteraction( PROPINTER_FIRE_FLAMMABLE ) )
  402. return;
  403. BaseClass::Ignite( flFlameLifetime, bNPCOnly, flSize, bCalledByLevelDesigner );
  404. if ( g_pGameRules->ShouldBurningPropsEmitLight() )
  405. {
  406. GetEffectEntity()->AddEffects( EF_DIMLIGHT );
  407. }
  408. // Frighten AIs, just in case this is an exploding thing.
  409. CSoundEnt::InsertSound( SOUND_DANGER, GetAbsOrigin(), 128.0f, 1.0f, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
  410. }
  411. //-----------------------------------------------------------------------------
  412. //-----------------------------------------------------------------------------
  413. void CBreakableProp::HandleFirstCollisionInteractions( int index, gamevcollisionevent_t *pEvent )
  414. {
  415. if ( pEvent->pEntities[ !index ]->IsWorld() )
  416. {
  417. if ( HasInteraction( PROPINTER_PHYSGUN_WORLD_STICK ) )
  418. {
  419. HandleInteractionStick( index, pEvent );
  420. }
  421. }
  422. if( HasInteraction( PROPINTER_PHYSGUN_FIRST_BREAK ) )
  423. {
  424. // Looks like it's best to break by having the object damage itself.
  425. CTakeDamageInfo info;
  426. info.SetDamage( m_iHealth );
  427. info.SetAttacker( this );
  428. info.SetInflictor( this );
  429. info.SetDamageType( DMG_GENERIC );
  430. Vector vecPosition;
  431. Vector vecVelocity;
  432. VPhysicsGetObject()->GetVelocity( &vecVelocity, NULL );
  433. VPhysicsGetObject()->GetPosition( &vecPosition, NULL );
  434. info.SetDamageForce( vecVelocity );
  435. info.SetDamagePosition( vecPosition );
  436. TakeDamage( info );
  437. return;
  438. }
  439. if( HasInteraction( PROPINTER_PHYSGUN_FIRST_PAINT ) )
  440. {
  441. IPhysicsObject *pObj = VPhysicsGetObject();
  442. Vector vecPos;
  443. pObj->GetPosition( &vecPos, NULL );
  444. Vector vecVelocity = pEvent->preVelocity[0];
  445. VectorNormalize(vecVelocity);
  446. trace_t tr;
  447. UTIL_TraceLine( vecPos, vecPos + (vecVelocity * 64), MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  448. if ( tr.m_pEnt )
  449. {
  450. #ifdef HL2_DLL
  451. // Don't paintsplat friendlies
  452. int iClassify = tr.m_pEnt->Classify();
  453. if ( iClassify != CLASS_PLAYER_ALLY_VITAL && iClassify != CLASS_PLAYER_ALLY &&
  454. iClassify != CLASS_CITIZEN_PASSIVE && iClassify != CLASS_CITIZEN_REBEL )
  455. #endif
  456. {
  457. switch( entindex() % 3 )
  458. {
  459. case 0:
  460. UTIL_DecalTrace( &tr, "PaintSplatBlue" );
  461. break;
  462. case 1:
  463. UTIL_DecalTrace( &tr, "PaintSplatGreen" );
  464. break;
  465. case 2:
  466. UTIL_DecalTrace( &tr, "PaintSplatPink" );
  467. break;
  468. }
  469. }
  470. }
  471. }
  472. if ( HasInteraction( PROPINTER_PHYSGUN_NOTIFY_CHILDREN ) )
  473. {
  474. CUtlVector<CBaseEntity *> children;
  475. GetAllChildren( this, children );
  476. for (int i = 0; i < children.Count(); i++ )
  477. {
  478. CBaseEntity *pent = children.Element( i );
  479. IParentPropInteraction *pPropInter = dynamic_cast<IParentPropInteraction *>( pent );
  480. if ( pPropInter )
  481. {
  482. pPropInter->OnParentCollisionInteraction( COLLISIONINTER_PARENT_FIRST_IMPACT, index, pEvent );
  483. }
  484. }
  485. }
  486. }
  487. void CBreakableProp::CheckRemoveRagdolls()
  488. {
  489. if ( HasSpawnFlags( SF_PHYSPROP_HAS_ATTACHED_RAGDOLLS ) )
  490. {
  491. DetachAttachedRagdollsForEntity( this );
  492. RemoveSpawnFlags( SF_PHYSPROP_HAS_ATTACHED_RAGDOLLS );
  493. }
  494. }
  495. //-----------------------------------------------------------------------------
  496. // Purpose: Handle special physgun interactions
  497. // Input : index -
  498. // *pEvent -
  499. //-----------------------------------------------------------------------------
  500. void CPhysicsProp::HandleAnyCollisionInteractions( int index, gamevcollisionevent_t *pEvent )
  501. {
  502. // If we're supposed to impale, and we've hit an NPC, impale it
  503. if ( HasInteraction( PROPINTER_PHYSGUN_FIRST_IMPALE ) )
  504. {
  505. Vector vel = pEvent->preVelocity[index];
  506. Vector forward;
  507. QAngle angImpaleForward;
  508. if ( GetPropDataAngles( "impale_forward", angImpaleForward ) )
  509. {
  510. Vector vecImpaleForward;
  511. AngleVectors( angImpaleForward, &vecImpaleForward );
  512. VectorRotate( vecImpaleForward, EntityToWorldTransform(), forward );
  513. }
  514. else
  515. {
  516. GetVectors( &forward, NULL, NULL );
  517. }
  518. float speed = DotProduct( forward, vel );
  519. if ( speed < 1000.0f )
  520. {
  521. // not going to stick, so remove any ragdolls we've got
  522. CheckRemoveRagdolls();
  523. return;
  524. }
  525. CBaseEntity *pHitEntity = pEvent->pEntities[!index];
  526. if ( pHitEntity->IsWorld() )
  527. {
  528. Vector normal;
  529. float sign = index ? -1.0f : 1.0f;
  530. pEvent->pInternalData->GetSurfaceNormal( normal );
  531. float dot = DotProduct( forward, normal );
  532. if ( (sign*dot) < DOT_45DEGREE )
  533. return;
  534. // Impale sticks to the wall if we hit end on
  535. HandleInteractionStick( index, pEvent );
  536. }
  537. else if ( pHitEntity->MyNPCPointer() )
  538. {
  539. CAI_BaseNPC *pNPC = pHitEntity->MyNPCPointer();
  540. IPhysicsObject *pObj = VPhysicsGetObject();
  541. // do not impale NPCs if the impaler is friendly
  542. CBasePlayer *pAttacker = HasPhysicsAttacker( 25.0f );
  543. if (pAttacker && pNPC->IRelationType( pAttacker ) == D_LI)
  544. {
  545. return;
  546. }
  547. Vector vecPos;
  548. pObj->GetPosition( &vecPos, NULL );
  549. // Find the bone for the hitbox we hit
  550. trace_t tr;
  551. UTIL_TraceLine( vecPos, vecPos + pEvent->preVelocity[index] * 1.5, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  552. Vector vecImpalePos = tr.endpos;
  553. int iBone = -1;
  554. if ( tr.hitbox )
  555. {
  556. Vector vecBonePos;
  557. QAngle vecBoneAngles;
  558. iBone = pNPC->GetHitboxBone( tr.hitbox );
  559. pNPC->GetBonePosition( iBone, vecBonePos, vecBoneAngles );
  560. Teleport( &vecBonePos, NULL, NULL );
  561. vecImpalePos = vecBonePos;
  562. }
  563. // Kill the NPC and make an attached ragdoll
  564. pEvent->pInternalData->GetContactPoint( vecImpalePos );
  565. CBaseEntity *pRagdoll = CreateServerRagdollAttached( pNPC, vec3_origin, -1, COLLISION_GROUP_INTERACTIVE_DEBRIS, pObj, this, 0, vecImpalePos, iBone, vec3_origin );
  566. if ( pRagdoll )
  567. {
  568. Vector vecVelocity = pEvent->preVelocity[index] * pObj->GetMass();
  569. PhysCallbackImpulse( pObj, vecVelocity, vec3_origin );
  570. UTIL_Remove( pNPC );
  571. AddSpawnFlags( SF_PHYSPROP_HAS_ATTACHED_RAGDOLLS );
  572. }
  573. }
  574. }
  575. }
  576. void CBreakableProp::StickAtPosition( const Vector &stickPosition, const Vector &savePosition, const QAngle &saveAngles )
  577. {
  578. if ( !VPhysicsGetObject()->IsMotionEnabled() )
  579. return;
  580. EmitSound("Metal.SawbladeStick");
  581. Teleport( &stickPosition, NULL, NULL );
  582. SetEnableMotionPosition( savePosition, saveAngles ); // this uses hierarchy, so it must be set after teleport
  583. VPhysicsGetObject()->EnableMotion( false );
  584. AddSpawnFlags( SF_PHYSPROP_ENABLE_ON_PHYSCANNON );
  585. SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  586. }
  587. //-----------------------------------------------------------------------------
  588. // Purpose:
  589. // Input : index -
  590. // *pEvent -
  591. //-----------------------------------------------------------------------------
  592. void CBreakableProp::HandleInteractionStick( int index, gamevcollisionevent_t *pEvent )
  593. {
  594. Vector vecDir = pEvent->preVelocity[ index ];
  595. float speed = VectorNormalize( vecDir );
  596. // Make sure the object is travelling fast enough to stick.
  597. if( speed > 1000.0f )
  598. {
  599. Vector position;
  600. QAngle angles;
  601. VPhysicsGetObject()->GetPosition( &position, &angles );
  602. Vector vecNormal;
  603. pEvent->pInternalData->GetSurfaceNormal( vecNormal );
  604. // we want the normal that points away from this object
  605. if ( index == 1 )
  606. {
  607. vecNormal *= -1.0f;
  608. }
  609. float flDot = DotProduct( vecDir, vecNormal );
  610. // Make sure the object isn't hitting the world at too sharp an angle.
  611. if( flDot > 0.3 )
  612. {
  613. // Finally, inhibit sticking in metal, grates, sky, or anything else that doesn't make a sound.
  614. const surfacedata_t *psurf = physprops->GetSurfaceData( pEvent->surfaceProps[!index] );
  615. if (psurf->game.material != CHAR_TEX_METAL && psurf->game.material != CHAR_TEX_GRATE && psurf->game.material != 'X' )
  616. {
  617. Vector savePosition = position;
  618. Vector vecEmbed = pEvent->preVelocity[ index ];
  619. VectorNormalize( vecEmbed );
  620. vecEmbed *= 8;
  621. position += vecEmbed;
  622. g_PostSimulationQueue.QueueCall( this, &CBreakableProp::StickAtPosition, position, savePosition, angles );
  623. }
  624. }
  625. }
  626. }
  627. //-----------------------------------------------------------------------------
  628. // Purpose: Turn on prop debugging mode
  629. //-----------------------------------------------------------------------------
  630. void CC_Prop_Debug( void )
  631. {
  632. // Toggle the prop debug bit on all props
  633. for ( CBaseEntity *pEntity = gEntList.FirstEnt(); pEntity != NULL; pEntity = gEntList.NextEnt(pEntity) )
  634. {
  635. CBaseProp *pProp = dynamic_cast<CBaseProp*>(pEntity);
  636. if ( pProp )
  637. {
  638. if ( pProp->m_debugOverlays & OVERLAY_PROP_DEBUG )
  639. {
  640. pProp->m_debugOverlays &= ~OVERLAY_PROP_DEBUG;
  641. }
  642. else
  643. {
  644. pProp->m_debugOverlays |= OVERLAY_PROP_DEBUG;
  645. }
  646. }
  647. }
  648. }
  649. static ConCommand prop_debug("prop_debug", CC_Prop_Debug, "Toggle prop debug mode. If on, props will show colorcoded bounding boxes. Red means ignore all damage. White means respond physically to damage but never break. Green maps health in the range of 100 down to 1.", FCVAR_CHEAT);
  650. void SendProxy_UnmodifiedQAngles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID )
  651. {
  652. QAngle *v = (QAngle*)pData;
  653. pOut->m_Vector[0] = v->x;
  654. pOut->m_Vector[1] = v->y;
  655. pOut->m_Vector[2] = v->z;
  656. }
  657. //=============================================================================================================
  658. // BREAKABLE PROPS
  659. //=============================================================================================================
  660. IMPLEMENT_SERVERCLASS_ST(CBreakableProp, DT_BreakableProp)
  661. SendPropQAngles( SENDINFO( m_qPreferredPlayerCarryAngles ), 0, SPROP_NOSCALE, SendProxy_UnmodifiedQAngles ),
  662. SendPropBool( SENDINFO( m_bClientPhysics ) ),
  663. END_SEND_TABLE()
  664. BEGIN_DATADESC( CBreakableProp )
  665. DEFINE_KEYFIELD( m_explodeDamage, FIELD_FLOAT, "ExplodeDamage"),
  666. DEFINE_KEYFIELD( m_explodeRadius, FIELD_FLOAT, "ExplodeRadius"),
  667. DEFINE_KEYFIELD( m_iMinHealthDmg, FIELD_INTEGER, "minhealthdmg" ),
  668. DEFINE_FIELD( m_createTick, FIELD_INTEGER ),
  669. DEFINE_FIELD( m_hBreaker, FIELD_EHANDLE ),
  670. DEFINE_KEYFIELD( m_PerformanceMode, FIELD_INTEGER, "PerformanceMode" ),
  671. DEFINE_FIELD( m_flDmgModBullet, FIELD_FLOAT ),
  672. DEFINE_FIELD( m_flDmgModClub, FIELD_FLOAT ),
  673. DEFINE_FIELD( m_flDmgModExplosive, FIELD_FLOAT ),
  674. DEFINE_FIELD( m_flDmgModFire, FIELD_FLOAT ),
  675. DEFINE_FIELD( m_iszPhysicsDamageTableName, FIELD_STRING ),
  676. DEFINE_FIELD( m_iszBreakableModel, FIELD_STRING ),
  677. DEFINE_FIELD( m_iBreakableSkin, FIELD_INTEGER ),
  678. DEFINE_FIELD( m_iBreakableCount, FIELD_INTEGER ),
  679. DEFINE_FIELD( m_iMaxBreakableSize, FIELD_INTEGER ),
  680. DEFINE_FIELD( m_iszBasePropData, FIELD_STRING ),
  681. DEFINE_FIELD( m_iInteractions, FIELD_INTEGER ),
  682. DEFINE_FIELD( m_iNumBreakableChunks, FIELD_INTEGER ),
  683. DEFINE_FIELD( m_nPhysgunState, FIELD_CHARACTER ),
  684. DEFINE_KEYFIELD( m_iszPuntSound, FIELD_STRING, "puntsound" ),
  685. DEFINE_KEYFIELD( m_flPressureDelay, FIELD_FLOAT, "PressureDelay" ),
  686. DEFINE_FIELD( m_preferredCarryAngles, FIELD_VECTOR ),
  687. DEFINE_FIELD( m_flDefaultFadeScale, FIELD_FLOAT ),
  688. DEFINE_FIELD( m_bUsePuntSound, FIELD_BOOLEAN ),
  689. // DEFINE_FIELD( m_mpBreakMode, mp_break_t ),
  690. // Inputs
  691. DEFINE_INPUTFUNC( FIELD_VOID, "Break", InputBreak ),
  692. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetHealth", InputSetHealth ),
  693. DEFINE_INPUTFUNC( FIELD_INTEGER, "AddHealth", InputAddHealth ),
  694. DEFINE_INPUTFUNC( FIELD_INTEGER, "RemoveHealth", InputRemoveHealth ),
  695. DEFINE_INPUT( m_impactEnergyScale, FIELD_FLOAT, "physdamagescale" ),
  696. DEFINE_INPUTFUNC( FIELD_VOID, "EnablePhyscannonPickup", InputEnablePhyscannonPickup ),
  697. DEFINE_INPUTFUNC( FIELD_VOID, "DisablePhyscannonPickup", InputDisablePhyscannonPickup ),
  698. DEFINE_INPUTFUNC( FIELD_VOID, "EnablePuntSound", InputEnablePuntSound ),
  699. DEFINE_INPUTFUNC( FIELD_VOID, "DisablePuntSound", InputDisablePuntSound ),
  700. // Outputs
  701. DEFINE_OUTPUT( m_OnBreak, "OnBreak" ),
  702. DEFINE_OUTPUT( m_OnHealthChanged, "OnHealthChanged" ),
  703. DEFINE_OUTPUT( m_OnTakeDamage, "OnTakeDamage" ),
  704. DEFINE_OUTPUT( m_OnPhysCannonDetach, "OnPhysCannonDetach" ),
  705. DEFINE_OUTPUT( m_OnPhysCannonAnimatePreStarted, "OnPhysCannonAnimatePreStarted" ),
  706. DEFINE_OUTPUT( m_OnPhysCannonAnimatePullStarted, "OnPhysCannonAnimatePullStarted" ),
  707. DEFINE_OUTPUT( m_OnPhysCannonAnimatePostStarted, "OnPhysCannonAnimatePostStarted" ),
  708. DEFINE_OUTPUT( m_OnPhysCannonPullAnimFinished, "OnPhysCannonPullAnimFinished" ),
  709. // Function Pointers
  710. DEFINE_THINKFUNC( BreakThink ),
  711. DEFINE_THINKFUNC( AnimateThink ),
  712. DEFINE_THINKFUNC( RampToDefaultFadeScale ),
  713. DEFINE_ENTITYFUNC( BreakablePropTouch ),
  714. // Physics Influence
  715. DEFINE_FIELD( m_hPhysicsAttacker, FIELD_EHANDLE ),
  716. DEFINE_FIELD( m_flLastPhysicsInfluenceTime, FIELD_TIME ),
  717. DEFINE_FIELD( m_bOriginalBlockLOS, FIELD_BOOLEAN ),
  718. DEFINE_FIELD( m_bBlockLOSSetByPropData, FIELD_BOOLEAN ),
  719. DEFINE_FIELD( m_bIsWalkableSetByPropData, FIELD_BOOLEAN ),
  720. // Damage
  721. DEFINE_FIELD( m_hLastAttacker, FIELD_EHANDLE ),
  722. DEFINE_FIELD( m_hFlareEnt, FIELD_EHANDLE ),
  723. END_DATADESC()
  724. //-----------------------------------------------------------------------------
  725. // Constructor:
  726. //-----------------------------------------------------------------------------
  727. CBreakableProp::CBreakableProp()
  728. {
  729. SetFadeDistance( -1.0f, 0.0f );
  730. SetGlobalFadeScale( 1.0f );
  731. m_flDefaultFadeScale = 1;
  732. m_mpBreakMode = MULTIPLAYER_BREAK_DEFAULT;
  733. // this is the default unless we are a prop_physics_multiplayer
  734. SetPhysicsMode( PHYSICS_MULTIPLAYER_SOLID );
  735. // This defaults to on. Most times mapmakers won't specify a punt sound to play.
  736. m_bUsePuntSound = true;
  737. m_qPreferredPlayerCarryAngles.GetForModify().Init( FLT_MAX, FLT_MAX, FLT_MAX );
  738. }
  739. //-----------------------------------------------------------------------------
  740. // Purpose:
  741. //-----------------------------------------------------------------------------
  742. void CBreakableProp::Spawn()
  743. {
  744. // Starts out as the default fade scale value
  745. m_flDefaultFadeScale = GetGlobalFadeScale();
  746. // Initialize damage modifiers. Must be done before baseclass spawn.
  747. m_flDmgModBullet = 1.0;
  748. m_flDmgModClub = 1.0;
  749. m_flDmgModExplosive = 1.0;
  750. m_flDmgModFire = 1.0f;
  751. BaseClass::Spawn();
  752. if ( IsMarkedForDeletion() )
  753. return;
  754. CStudioHdr *pStudioHdr = GetModelPtr( );
  755. if ( pStudioHdr->flags() & STUDIOHDR_FLAGS_NO_FORCED_FADE )
  756. {
  757. DisableAutoFade();
  758. }
  759. else
  760. {
  761. SetGlobalFadeScale( m_flDefaultFadeScale );
  762. }
  763. // If we have no custom breakable chunks, see if we're breaking into generic ones
  764. if ( !m_iNumBreakableChunks )
  765. {
  766. IBreakableWithPropData *pBreakableInterface = assert_cast<IBreakableWithPropData*>(this);
  767. if ( pBreakableInterface->GetBreakableModel() != NULL_STRING && pBreakableInterface->GetBreakableCount() )
  768. {
  769. m_iNumBreakableChunks = pBreakableInterface->GetBreakableCount();
  770. }
  771. }
  772. // Setup takedamage based upon the health we parsed earlier, and our interactions
  773. if ( ( m_iHealth == 0 ) ||
  774. ( !m_iNumBreakableChunks &&
  775. !HasInteraction( PROPINTER_PHYSGUN_BREAK_EXPLODE ) &&
  776. !HasInteraction( PROPINTER_PHYSGUN_BREAK_EXPLODE_ICE ) &&
  777. !HasInteraction( PROPINTER_PHYSGUN_FIRST_BREAK ) &&
  778. !HasInteraction( PROPINTER_FIRE_FLAMMABLE ) &&
  779. !HasInteraction( PROPINTER_FIRE_IGNITE_HALFHEALTH ) &&
  780. !HasInteraction( PROPINTER_MELEE_IMMUNE ) &&
  781. !HasInteraction( PROPINTER_FIRE_EXPLOSIVE_RESIST ) ) )
  782. {
  783. m_iHealth = 0;
  784. m_takedamage = DAMAGE_EVENTS_ONLY;
  785. }
  786. else
  787. {
  788. m_takedamage = DAMAGE_YES;
  789. if( g_pGameRules->GetAutoAimMode() == AUTOAIM_ON_CONSOLE )
  790. {
  791. if ( HasInteraction( PROPINTER_PHYSGUN_BREAK_EXPLODE ) ||
  792. HasInteraction( PROPINTER_PHYSGUN_BREAK_EXPLODE_ICE ) ||
  793. HasInteraction( PROPINTER_FIRE_IGNITE_HALFHEALTH ) )
  794. {
  795. // Exploding barrels, exploding gas cans
  796. AddFlag( FL_AIMTARGET );
  797. }
  798. }
  799. }
  800. m_iMaxHealth = ( m_iHealth > 0 ) ? m_iHealth : 1;
  801. m_createTick = gpGlobals->tickcount;
  802. if ( m_impactEnergyScale == 0 )
  803. {
  804. m_impactEnergyScale = 0.1f;
  805. }
  806. m_preferredCarryAngles = QAngle( -5, 0, 0 );
  807. // The presence of this activity causes us to have to detach it before it can be grabbed.
  808. if ( SelectWeightedSequence( ACT_PHYSCANNON_ANIMATE ) != ACTIVITY_NOT_AVAILABLE )
  809. {
  810. m_nPhysgunState = PHYSGUN_ANIMATE_ON_PULL;
  811. }
  812. else if ( SelectWeightedSequence( ACT_PHYSCANNON_DETACH ) != ACTIVITY_NOT_AVAILABLE )
  813. {
  814. m_nPhysgunState = PHYSGUN_MUST_BE_DETACHED;
  815. }
  816. else
  817. {
  818. m_nPhysgunState = PHYSGUN_CAN_BE_GRABBED;
  819. }
  820. m_hLastAttacker = NULL;
  821. m_hBreaker = NULL;
  822. SetTouch( &CBreakableProp::BreakablePropTouch );
  823. }
  824. void CBreakableProp::UpdateOnRemove()
  825. {
  826. BaseClass::UpdateOnRemove();
  827. }
  828. //-----------------------------------------------------------------------------
  829. // Disable auto fading under dx7 or when level fades are specified
  830. //-----------------------------------------------------------------------------
  831. void CBreakableProp::DisableAutoFade()
  832. {
  833. SetGlobalFadeScale( 0.0f );
  834. m_flDefaultFadeScale = 0;
  835. }
  836. //-----------------------------------------------------------------------------
  837. // Copy fade from another breakable.
  838. //-----------------------------------------------------------------------------
  839. void CBreakableProp::CopyFadeFrom( CBreakableProp *pSource )
  840. {
  841. m_flDefaultFadeScale = pSource->m_flDefaultFadeScale;
  842. SetGlobalFadeScale( pSource->GetGlobalFadeScale() );
  843. if ( GetGlobalFadeScale() != m_flDefaultFadeScale )
  844. {
  845. float flNextThink = pSource->GetNextThink( s_pFadeScaleThink );
  846. if ( flNextThink < gpGlobals->curtime + TICK_INTERVAL )
  847. {
  848. flNextThink = gpGlobals->curtime + TICK_INTERVAL;
  849. }
  850. SetContextThink( &CBreakableProp::RampToDefaultFadeScale, flNextThink, s_pFadeScaleThink );
  851. }
  852. }
  853. //-----------------------------------------------------------------------------
  854. // Make physcannonable, or not
  855. //-----------------------------------------------------------------------------
  856. void CBreakableProp::InputEnablePhyscannonPickup( inputdata_t &inputdata )
  857. {
  858. RemoveEFlags( EFL_NO_PHYSCANNON_INTERACTION );
  859. }
  860. void CBreakableProp::InputDisablePhyscannonPickup( inputdata_t &inputdata )
  861. {
  862. AddEFlags( EFL_NO_PHYSCANNON_INTERACTION );
  863. }
  864. //-----------------------------------------------------------------------------
  865. // Purpose:
  866. // Input : *pOther -
  867. //-----------------------------------------------------------------------------
  868. void CBreakableProp::BreakablePropTouch( CBaseEntity *pOther )
  869. {
  870. if ( HasSpawnFlags( SF_PHYSPROP_TOUCH ) )
  871. {
  872. // can be broken when run into
  873. float flDamage = pOther->GetSmoothedVelocity().Length() * 0.01;
  874. if ( flDamage >= m_iHealth )
  875. {
  876. // Make sure we can take damage
  877. m_takedamage = DAMAGE_YES;
  878. OnTakeDamage( CTakeDamageInfo( pOther, pOther, flDamage, DMG_CRUSH ) );
  879. // do a little damage to player if we broke glass or computer
  880. CTakeDamageInfo info( pOther, pOther, flDamage/4, DMG_SLASH );
  881. CalculateMeleeDamageForce( &info, (pOther->GetAbsOrigin() - GetAbsOrigin()), GetAbsOrigin() );
  882. pOther->TakeDamage( info );
  883. }
  884. }
  885. if ( HasSpawnFlags( SF_PHYSPROP_PRESSURE ) && pOther->GetGroundEntity() == this )
  886. {
  887. // can be broken when stood upon
  888. // play creaking sound here.
  889. // DamageSound();
  890. m_hBreaker = pOther;
  891. if ( m_pfnThink != (void (CBaseEntity::*)())&CBreakableProp::BreakThink )
  892. {
  893. SetThink( &CBreakableProp::BreakThink );
  894. //SetTouch( NULL );
  895. // Add optional delay
  896. SetNextThink( gpGlobals->curtime + m_flPressureDelay );
  897. }
  898. }
  899. #ifdef HL2_EPISODIC
  900. if ( m_hFlareEnt )
  901. {
  902. CAI_BaseNPC *pNPC = pOther->MyNPCPointer();
  903. if ( pNPC && pNPC->AllowedToIgnite() && pNPC->IsOnFire() == false )
  904. {
  905. pNPC->Ignite( 25.0f );
  906. KillFlare( this, m_hFlareEnt, PROP_FLARE_IGNITE_SUBSTRACT );
  907. IGameEvent *event = gameeventmanager->CreateEvent( "flare_ignite_npc" );
  908. if ( event )
  909. {
  910. event->SetInt( "entindex", pNPC->entindex() );
  911. gameeventmanager->FireEvent( event );
  912. }
  913. }
  914. }
  915. #endif
  916. }
  917. //-----------------------------------------------------------------------------
  918. // UNDONE: Time stamp the object's creation so that an explosion or something doesn't break the parent object
  919. // and then break the children who spawn afterward ?
  920. // Explosions should use entities in box before they start to do damage. Make sure nothing traverses the list
  921. // in a way that would hose this.
  922. int CBreakableProp::OnTakeDamage( const CTakeDamageInfo &inputInfo )
  923. {
  924. CTakeDamageInfo info = inputInfo;
  925. // If attacker can't do at least the MIN required damage to us, don't take any damage from them
  926. if ( info.GetDamage() < m_iMinHealthDmg )
  927. return 0;
  928. if (!PassesDamageFilter( info ))
  929. {
  930. return 1;
  931. }
  932. if( info.GetAttacker() && info.GetAttacker()->MyCombatCharacterPointer() )
  933. {
  934. m_hLastAttacker.Set( info.GetAttacker() );
  935. }
  936. else if ( info.GetAttacker() )
  937. {
  938. CBaseEntity *attacker = info.GetAttacker();
  939. CBaseEntity *attackerOwner = attacker->GetOwnerEntity();
  940. if ( attackerOwner && attackerOwner->MyCombatCharacterPointer() )
  941. {
  942. m_hLastAttacker.Set( attackerOwner );
  943. }
  944. }
  945. float flPropDamage = GetBreakableDamage( info, assert_cast<IBreakableWithPropData*>(this) );
  946. info.SetDamage( flPropDamage );
  947. // If attacker can't do at least the MIN required damage to us, don't take any damage from them
  948. if ( info.GetDamage() < m_iMinHealthDmg )
  949. return 0;
  950. // UNDONE: Do this?
  951. #if 0
  952. // Make a shard noise each time func breakable is hit.
  953. // Don't play shard noise if being burned.
  954. // Don't play shard noise if cbreakable actually died.
  955. if ( ( bitsDamageType & DMG_BURN ) == false )
  956. {
  957. DamageSound();
  958. }
  959. #endif
  960. // don't take damage on the same frame you were created
  961. // (avoids a set of explosions progressively vaporizing a compound breakable)
  962. if ( m_createTick == (unsigned int)gpGlobals->tickcount )
  963. {
  964. int saveFlags = m_takedamage;
  965. m_takedamage = DAMAGE_EVENTS_ONLY;
  966. int ret = BaseClass::OnTakeDamage( info );
  967. m_takedamage = saveFlags;
  968. return ret;
  969. }
  970. // Ignore fire damage from other flames if I'm already on fire.
  971. // (i.e., only let the flames attached to me damage me)
  972. if( IsOnFire() && (inputInfo.GetDamageType() & DMG_BURN) && !(inputInfo.GetDamageType() & DMG_DIRECT) )
  973. {
  974. return 0;
  975. }
  976. bool bDeadly = info.GetDamage() >= m_iHealth;
  977. // Handle melee attack immunity
  978. if ( ((info.GetDamageType() & DMG_CLUB) || (info.GetDamageType() & DMG_SLASH)) && HasInteraction( PROPINTER_MELEE_IMMUNE ) )
  979. {
  980. int saveFlags = m_takedamage;
  981. m_takedamage = DAMAGE_EVENTS_ONLY;
  982. int ret = BaseClass::OnTakeDamage( info );
  983. m_takedamage = saveFlags;
  984. return ret;
  985. }
  986. if( bDeadly && (info.GetDamageType() & DMG_BLAST) && HasInteraction( PROPINTER_FIRE_EXPLOSIVE_RESIST ) && info.GetInflictor() )
  987. {
  988. // This explosion would kill me, but I have a special interaction with explosions.
  989. float flDist = ( WorldSpaceCenter() - info.GetInflictor()->WorldSpaceCenter() ).Length();
  990. // I'm going to burn for a bit instead of exploding right now.
  991. float flBurnTime;
  992. if( flDist >= PROP_EXPLOSION_IGNITE_RADIUS )
  993. {
  994. // I'm far from the blast. Ignite and burn for several seconds.
  995. const float MAX_BLAST_DIST = 256.0f;
  996. // Just clamp distance.
  997. if( flDist > MAX_BLAST_DIST )
  998. flDist = MAX_BLAST_DIST;
  999. float flFactor;
  1000. flFactor = flDist / MAX_BLAST_DIST;
  1001. const float MAX_BURN_TIME = 5.0f;
  1002. flBurnTime = MAX( 0.5, MAX_BURN_TIME * flFactor );
  1003. flBurnTime += random->RandomFloat( 0, 0.5 );
  1004. }
  1005. else
  1006. {
  1007. // Very near the explosion. explode almost immediately.
  1008. flBurnTime = random->RandomFloat( 0.1, 0.2 );
  1009. }
  1010. // Change my health so that I burn for flBurnTime seconds.
  1011. float flIdealHealth = fpmin( m_iHealth, FLAME_DIRECT_DAMAGE_PER_SEC * flBurnTime );
  1012. float flIdealDamage = m_iHealth - flIdealHealth;
  1013. // Scale the damage to do ideal damage.
  1014. info.ScaleDamage( flIdealDamage / info.GetDamage() );
  1015. // Re-evaluate the deadly
  1016. bDeadly = info.GetDamage() >= m_iHealth;
  1017. }
  1018. if ( !bDeadly && (info.GetDamageType() & DMG_BLAST) )
  1019. {
  1020. Ignite( random->RandomFloat( 10, 15 ), false );
  1021. }
  1022. else if ( !bDeadly && (info.GetDamageType() & DMG_BURN) )
  1023. {
  1024. // Ignite if burned, and flammable (the Ignite() function takes care of all of this).
  1025. Ignite( random->RandomFloat( 10, 15 ), false );
  1026. }
  1027. else if ( !bDeadly && (info.GetDamageType() & DMG_BULLET) )
  1028. {
  1029. if ( HasInteraction( PROPINTER_FIRE_IGNITE_HALFHEALTH ) )
  1030. {
  1031. if ( (m_iHealth - info.GetDamage()) <= m_iMaxHealth / 2 && !IsOnFire() )
  1032. {
  1033. // Bump back up to full health so it burns longer. Magically getting health back isn't
  1034. // a big problem because if this item takes damage again whilst burning, it will break.
  1035. m_iHealth = m_iMaxHealth;
  1036. Ignite( random->RandomFloat( 10, 15 ), false );
  1037. }
  1038. else if ( IsOnFire() )
  1039. {
  1040. // Explode right now!
  1041. info.ScaleDamage( m_iHealth / info.GetDamage() );
  1042. }
  1043. }
  1044. }
  1045. int ret = BaseClass::OnTakeDamage( info );
  1046. // Output the new health as a percentage of MAX health [0..1]
  1047. float flRatio = clamp( (float)m_iHealth / (float)m_iMaxHealth, 0, 1 );
  1048. m_OnHealthChanged.Set( flRatio, info.GetAttacker(), this );
  1049. m_OnTakeDamage.FireOutput( info.GetAttacker(), this );
  1050. return ret;
  1051. }
  1052. //-----------------------------------------------------------------------------
  1053. // Purpose:
  1054. //-----------------------------------------------------------------------------
  1055. void CBreakableProp::Event_Killed( const CTakeDamageInfo &info )
  1056. {
  1057. IPhysicsObject *pPhysics = VPhysicsGetObject();
  1058. if ( pPhysics && !pPhysics->IsMoveable() )
  1059. {
  1060. pPhysics->EnableMotion( true );
  1061. VPhysicsTakeDamage( info );
  1062. }
  1063. Break( info.GetInflictor(), info );
  1064. BaseClass::Event_Killed( info );
  1065. }
  1066. //-----------------------------------------------------------------------------
  1067. // Purpose: Input handler for breaking the breakable immediately.
  1068. //-----------------------------------------------------------------------------
  1069. void CBreakableProp::InputBreak( inputdata_t &inputdata )
  1070. {
  1071. CTakeDamageInfo info;
  1072. info.SetAttacker( this );
  1073. Break( inputdata.pActivator, info );
  1074. }
  1075. //-----------------------------------------------------------------------------
  1076. // Purpose: Input handler for adding to the breakable's health.
  1077. // Input : Integer health points to add.
  1078. //-----------------------------------------------------------------------------
  1079. void CBreakableProp::InputAddHealth( inputdata_t &inputdata )
  1080. {
  1081. UpdateHealth( m_iHealth + inputdata.value.Int(), inputdata.pActivator );
  1082. }
  1083. //-----------------------------------------------------------------------------
  1084. // Purpose: Input handler for removing health from the breakable.
  1085. // Input : Integer health points to remove.
  1086. //-----------------------------------------------------------------------------
  1087. void CBreakableProp::InputRemoveHealth( inputdata_t &inputdata )
  1088. {
  1089. UpdateHealth( m_iHealth - inputdata.value.Int(), inputdata.pActivator );
  1090. }
  1091. //-----------------------------------------------------------------------------
  1092. // Purpose: Input handler for setting the breakable's health.
  1093. //-----------------------------------------------------------------------------
  1094. void CBreakableProp::InputSetHealth( inputdata_t &inputdata )
  1095. {
  1096. UpdateHealth( inputdata.value.Int(), inputdata.pActivator );
  1097. }
  1098. //-----------------------------------------------------------------------------
  1099. // Purpose: Choke point for changes to breakable health. Ensures outputs are fired.
  1100. // Input : iNewHealth -
  1101. // pActivator -
  1102. // Output : Returns true if the breakable survived, false if it died (broke).
  1103. //-----------------------------------------------------------------------------
  1104. bool CBreakableProp::UpdateHealth( int iNewHealth, CBaseEntity *pActivator )
  1105. {
  1106. if ( iNewHealth != m_iHealth )
  1107. {
  1108. m_iHealth = iNewHealth;
  1109. if ( m_iMaxHealth == 0 )
  1110. {
  1111. Assert( false );
  1112. m_iMaxHealth = 1;
  1113. }
  1114. // Output the new health as a percentage of MAX health [0..1]
  1115. float flRatio = clamp( (float)m_iHealth / (float)m_iMaxHealth, 0, 1 );
  1116. m_OnHealthChanged.Set( flRatio, pActivator, this );
  1117. if ( m_iHealth <= 0 )
  1118. {
  1119. CTakeDamageInfo info;
  1120. info.SetAttacker( this );
  1121. Break( pActivator, info );
  1122. return false;
  1123. }
  1124. }
  1125. return true;
  1126. }
  1127. //-----------------------------------------------------------------------------
  1128. // Purpose: Advance a ripped-off-animation frame
  1129. //-----------------------------------------------------------------------------
  1130. bool CBreakableProp::OnAttemptPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  1131. {
  1132. if ( m_nPhysgunState == PHYSGUN_CAN_BE_GRABBED )
  1133. return true;
  1134. if ( m_nPhysgunState == PHYSGUN_ANIMATE_FINISHED )
  1135. return false;
  1136. if ( m_nPhysgunState == PHYSGUN_MUST_BE_DETACHED )
  1137. {
  1138. // A punt advances
  1139. ResetSequence( SelectWeightedSequence( ACT_PHYSCANNON_DETACH ) );
  1140. SetPlaybackRate( 0.0f );
  1141. ResetClientsideFrame();
  1142. m_nPhysgunState = PHYSGUN_IS_DETACHING;
  1143. return false;
  1144. }
  1145. if ( m_nPhysgunState == PHYSGUN_ANIMATE_ON_PULL )
  1146. {
  1147. // Animation-requiring detachments ignore punts
  1148. if ( reason == PUNTED_BY_CANNON )
  1149. return false;
  1150. // Do we have a pre sequence?
  1151. int iSequence = SelectWeightedSequence( ACT_PHYSCANNON_ANIMATE_PRE );
  1152. if ( iSequence != ACTIVITY_NOT_AVAILABLE )
  1153. {
  1154. m_nPhysgunState = PHYSGUN_ANIMATE_IS_PRE_ANIMATING;
  1155. SetContextThink( &CBreakableProp::AnimateThink, gpGlobals->curtime + 0.1, s_pPropAnimateThink );
  1156. m_OnPhysCannonAnimatePreStarted.FireOutput( NULL,this );
  1157. }
  1158. else
  1159. {
  1160. // Go straight to the animate sequence
  1161. iSequence = SelectWeightedSequence( ACT_PHYSCANNON_ANIMATE );
  1162. m_nPhysgunState = PHYSGUN_ANIMATE_IS_ANIMATING;
  1163. m_OnPhysCannonAnimatePullStarted.FireOutput( NULL,this );
  1164. }
  1165. ResetSequence( iSequence );
  1166. SetPlaybackRate( 1.0f );
  1167. ResetClientsideFrame();
  1168. }
  1169. // If we're running PRE or POST ANIMATE sequences, wait for them to be done
  1170. if ( m_nPhysgunState == PHYSGUN_ANIMATE_IS_PRE_ANIMATING ||
  1171. m_nPhysgunState == PHYSGUN_ANIMATE_IS_POST_ANIMATING )
  1172. return false;
  1173. if ( m_nPhysgunState == PHYSGUN_ANIMATE_IS_ANIMATING )
  1174. {
  1175. // Animation-requiring detachments ignore punts
  1176. if ( reason == PUNTED_BY_CANNON )
  1177. return false;
  1178. StudioFrameAdvanceManual( gpGlobals->frametime );
  1179. DispatchAnimEvents( this );
  1180. if ( IsActivityFinished() )
  1181. {
  1182. int iSequence = SelectWeightedSequence( ACT_PHYSCANNON_ANIMATE_POST );
  1183. if ( iSequence != ACTIVITY_NOT_AVAILABLE )
  1184. {
  1185. m_nPhysgunState = PHYSGUN_ANIMATE_IS_POST_ANIMATING;
  1186. SetContextThink( &CBreakableProp::AnimateThink, gpGlobals->curtime + 0.1, s_pPropAnimateThink );
  1187. ResetSequence( iSequence );
  1188. SetPlaybackRate( 1.0f );
  1189. ResetClientsideFrame();
  1190. m_OnPhysCannonAnimatePostStarted.FireOutput( NULL,this );
  1191. }
  1192. else
  1193. {
  1194. m_nPhysgunState = PHYSGUN_ANIMATE_FINISHED;
  1195. m_OnPhysCannonPullAnimFinished.FireOutput( NULL,this );
  1196. }
  1197. }
  1198. }
  1199. else
  1200. {
  1201. // Here, we're grabbing it. If we try to punt it, advance frames by quite a bit.
  1202. StudioFrameAdvanceManual( (reason == PICKED_UP_BY_CANNON) ? gpGlobals->frametime : 0.5f );
  1203. ResetClientsideFrame();
  1204. DispatchAnimEvents( this );
  1205. if ( IsActivityFinished() )
  1206. {
  1207. // We're done, reset the playback rate.
  1208. SetPlaybackRate( 1.0f );
  1209. m_nPhysgunState = PHYSGUN_CAN_BE_GRABBED;
  1210. m_OnPhysCannonDetach.FireOutput( NULL,this );
  1211. }
  1212. }
  1213. return false;
  1214. }
  1215. //-----------------------------------------------------------------------------
  1216. // Physics Attacker
  1217. //-----------------------------------------------------------------------------
  1218. void CBreakableProp::AnimateThink( void )
  1219. {
  1220. if ( m_nPhysgunState == PHYSGUN_ANIMATE_IS_PRE_ANIMATING || m_nPhysgunState == PHYSGUN_ANIMATE_IS_POST_ANIMATING )
  1221. {
  1222. StudioFrameAdvanceManual( 0.1 );
  1223. DispatchAnimEvents( this );
  1224. SetNextThink( gpGlobals->curtime + 0.1, s_pPropAnimateThink );
  1225. if ( IsActivityFinished() )
  1226. {
  1227. if ( m_nPhysgunState == PHYSGUN_ANIMATE_IS_PRE_ANIMATING )
  1228. {
  1229. // Start the animate sequence
  1230. m_nPhysgunState = PHYSGUN_ANIMATE_IS_ANIMATING;
  1231. ResetSequence( SelectWeightedSequence( ACT_PHYSCANNON_ANIMATE ) );
  1232. SetPlaybackRate( 1.0f );
  1233. ResetClientsideFrame();
  1234. m_OnPhysCannonAnimatePullStarted.FireOutput( NULL,this );
  1235. }
  1236. else
  1237. {
  1238. m_nPhysgunState = PHYSGUN_ANIMATE_FINISHED;
  1239. m_OnPhysCannonPullAnimFinished.FireOutput( NULL,this );
  1240. }
  1241. SetContextThink( NULL, 0, s_pPropAnimateThink );
  1242. }
  1243. }
  1244. }
  1245. //-----------------------------------------------------------------------------
  1246. // Physics Attacker
  1247. //-----------------------------------------------------------------------------
  1248. void CBreakableProp::SetPhysicsAttacker( CBasePlayer *pEntity, float flTime )
  1249. {
  1250. m_hPhysicsAttacker = pEntity;
  1251. m_flLastPhysicsInfluenceTime = flTime;
  1252. //Msg( "Prop(%x) phys attacker set to %s.\n", this, pEntity ? pEntity->GetPlayerName() : "nobody" );
  1253. }
  1254. //-----------------------------------------------------------------------------
  1255. // Prevents fade scale from happening
  1256. //-----------------------------------------------------------------------------
  1257. void CBreakableProp::ForceFadeScaleToAlwaysVisible()
  1258. {
  1259. SetGlobalFadeScale( 0.0f );
  1260. SetContextThink( NULL, gpGlobals->curtime, s_pFadeScaleThink );
  1261. }
  1262. void CBreakableProp::RampToDefaultFadeScale()
  1263. {
  1264. // This fade scale ramp is performed automatically any time props such as weighted cubes
  1265. // are picked up, dropped, or launched by catapults. On low-end PC, this turns weighted
  1266. // cube fade distance back on, which we don't want. Don't do this for Portal 2.
  1267. #if !defined( PORTAL2 )
  1268. SetGlobalFadeScale( GetGlobalFadeScale() + m_flDefaultFadeScale * TICK_INTERVAL / 2.0f );
  1269. if ( GetGlobalFadeScale() >= m_flDefaultFadeScale )
  1270. {
  1271. SetGlobalFadeScale( m_flDefaultFadeScale );
  1272. SetContextThink( NULL, gpGlobals->curtime, s_pFadeScaleThink );
  1273. }
  1274. else
  1275. {
  1276. SetContextThink( &CBreakableProp::RampToDefaultFadeScale, gpGlobals->curtime + TICK_INTERVAL, s_pFadeScaleThink );
  1277. }
  1278. #endif
  1279. }
  1280. //-----------------------------------------------------------------------------
  1281. // Purpose: Keep track of physgun influence
  1282. //-----------------------------------------------------------------------------
  1283. void CBreakableProp::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  1284. {
  1285. // Make sure held objects are always visible
  1286. if ( reason == PICKED_UP_BY_CANNON )
  1287. {
  1288. ForceFadeScaleToAlwaysVisible();
  1289. }
  1290. else
  1291. {
  1292. SetContextThink( &CBreakableProp::RampToDefaultFadeScale, gpGlobals->curtime + 2.0f, s_pFadeScaleThink );
  1293. }
  1294. #ifdef PORTAL
  1295. if ( reason == PICKED_UP_BY_CANNON || reason == PICKED_UP_BY_PLAYER )
  1296. {
  1297. // Steal from another player if they were holding the object
  1298. CBasePlayer* pOtherPlayer = GetPlayerHoldingEntity( this );
  1299. if ( pOtherPlayer )
  1300. {
  1301. pOtherPlayer->ForceDropOfCarriedPhysObjects();
  1302. }
  1303. }
  1304. #endif
  1305. if( reason == PUNTED_BY_CANNON )
  1306. {
  1307. PlayPuntSound();
  1308. }
  1309. if ( IsGameConsole() )
  1310. {
  1311. if( reason != PUNTED_BY_CANNON && (pPhysGunUser->m_nNumCrateHudHints < NUM_SUPPLY_CRATE_HUD_HINTS) )
  1312. {
  1313. if( FClassnameIs( this, "item_item_crate") )
  1314. {
  1315. pPhysGunUser->m_nNumCrateHudHints++;
  1316. UTIL_HudHintText( pPhysGunUser, "#Valve_Hint_Hold_ItemCrate" );
  1317. }
  1318. }
  1319. }
  1320. SetPhysicsAttacker( pPhysGunUser, gpGlobals->curtime );
  1321. // Store original BlockLOS, and disable BlockLOS
  1322. m_bOriginalBlockLOS = BlocksLOS();
  1323. SetBlocksLOS( false );
  1324. #ifdef HL2_EPISODIC
  1325. if ( HasInteraction( PROPINTER_PHYSGUN_CREATE_FLARE ) )
  1326. {
  1327. CreateFlare( PROP_FLARE_LIFETIME );
  1328. }
  1329. #endif
  1330. }
  1331. #ifdef HL2_EPISODIC
  1332. //-----------------------------------------------------------------------------
  1333. // Purpose: Create a flare at the attachment point
  1334. //-----------------------------------------------------------------------------
  1335. void CBreakableProp::CreateFlare( float flLifetime )
  1336. {
  1337. // Create the flare
  1338. CBaseEntity *pFlare = ::CreateFlare( GetAbsOrigin(), GetAbsAngles(), this, flLifetime );
  1339. if ( pFlare )
  1340. {
  1341. int iAttachment = LookupAttachment( "fuse" );
  1342. Vector vOrigin;
  1343. GetAttachment( iAttachment, vOrigin );
  1344. pFlare->SetMoveType( MOVETYPE_NONE );
  1345. pFlare->SetSolid( SOLID_NONE );
  1346. pFlare->SetRenderMode( kRenderTransAlpha );
  1347. pFlare->SetRenderAlpha( 1 );
  1348. pFlare->SetLocalOrigin( vOrigin );
  1349. pFlare->SetParent( this, iAttachment );
  1350. RemoveInteraction( PROPINTER_PHYSGUN_CREATE_FLARE );
  1351. m_hFlareEnt = pFlare;
  1352. SetThink( &CBreakable::SUB_FadeOut );
  1353. SetNextThink( gpGlobals->curtime + flLifetime + 5.0f );
  1354. m_nSkin = 1;
  1355. AddEntityToDarknessCheck( pFlare );
  1356. AddEffects( EF_NOSHADOW );
  1357. }
  1358. }
  1359. #endif // HL2_EPISODIC
  1360. //-----------------------------------------------------------------------------
  1361. // Purpose:
  1362. //-----------------------------------------------------------------------------
  1363. void CBreakableProp::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
  1364. {
  1365. SetContextThink( &CBreakableProp::RampToDefaultFadeScale, gpGlobals->curtime + 2.0f, s_pFadeScaleThink );
  1366. SetPhysicsAttacker( pPhysGunUser, gpGlobals->curtime );
  1367. if( (int)Reason == (int)PUNTED_BY_CANNON )
  1368. {
  1369. PlayPuntSound();
  1370. }
  1371. // Restore original BlockLOS
  1372. SetBlocksLOS( m_bOriginalBlockLOS );
  1373. }
  1374. //-----------------------------------------------------------------------------
  1375. //-----------------------------------------------------------------------------
  1376. AngularImpulse CBreakableProp::PhysGunLaunchAngularImpulse()
  1377. {
  1378. if( HasInteraction( PROPINTER_PHYSGUN_LAUNCH_SPIN_NONE ) || HasInteraction( PROPINTER_PHYSGUN_LAUNCH_SPIN_Z ) )
  1379. {
  1380. // Don't add in random angular impulse if this object is supposed to spin in a specific way.
  1381. AngularImpulse ang( 0, 0, 0 );
  1382. return ang;
  1383. }
  1384. return CDefaultPlayerPickupVPhysics::PhysGunLaunchAngularImpulse();
  1385. }
  1386. //-----------------------------------------------------------------------------
  1387. //-----------------------------------------------------------------------------
  1388. CBasePlayer *CBreakableProp::HasPhysicsAttacker( float dt )
  1389. {
  1390. if (gpGlobals->curtime - dt <= m_flLastPhysicsInfluenceTime)
  1391. {
  1392. return m_hPhysicsAttacker;
  1393. }
  1394. return NULL;
  1395. }
  1396. //-----------------------------------------------------------------------------
  1397. // Purpose:
  1398. //-----------------------------------------------------------------------------
  1399. void CBreakableProp::BreakThink( void )
  1400. {
  1401. CTakeDamageInfo info;
  1402. info.SetAttacker( this );
  1403. Break( m_hBreaker, info );
  1404. }
  1405. //-----------------------------------------------------------------------------
  1406. // Purpose: Play the sound (if any) that I'm supposed to play when punted.
  1407. //-----------------------------------------------------------------------------
  1408. void CBreakableProp::PlayPuntSound()
  1409. {
  1410. if( !m_bUsePuntSound )
  1411. return;
  1412. if( m_iszPuntSound == NULL_STRING )
  1413. return;
  1414. EmitSound( STRING(m_iszPuntSound) );
  1415. }
  1416. //-----------------------------------------------------------------------------
  1417. // Purpose:
  1418. //-----------------------------------------------------------------------------
  1419. void CBreakableProp::Precache()
  1420. {
  1421. m_iNumBreakableChunks = PropBreakablePrecacheAll( GetModelName() );
  1422. if( m_iszPuntSound != NULL_STRING )
  1423. {
  1424. PrecacheScriptSound( STRING(m_iszPuntSound) );
  1425. }
  1426. BaseClass::Precache();
  1427. }
  1428. // Get the root physics object from which all broken pieces will
  1429. // derive their positions and velocities
  1430. IPhysicsObject *CBreakableProp::GetRootPhysicsObjectForBreak()
  1431. {
  1432. return VPhysicsGetObject();
  1433. }
  1434. static int g_BreakPropEvent = 0;
  1435. void CBreakableProp::Break( CBaseEntity *pBreaker, const CTakeDamageInfo &info )
  1436. {
  1437. const char *pModelName = STRING( GetModelName() );
  1438. if ( pModelName && Q_stristr( pModelName, "crate" ) )
  1439. {
  1440. bool bSmashed = false;
  1441. if ( pBreaker && pBreaker->IsPlayer() )
  1442. {
  1443. bSmashed = true;
  1444. }
  1445. else if ( m_hPhysicsAttacker.Get() && m_hPhysicsAttacker->IsPlayer() )
  1446. {
  1447. bSmashed = true;
  1448. }
  1449. else if ( pBreaker && dynamic_cast< CPropVehicleDriveable * >( pBreaker ) )
  1450. {
  1451. CPropVehicleDriveable *veh = static_cast< CPropVehicleDriveable * >( pBreaker );
  1452. CBaseEntity *driver = veh->GetDriver();
  1453. if ( driver && driver->IsPlayer() )
  1454. {
  1455. bSmashed = true;
  1456. }
  1457. }
  1458. if ( bSmashed )
  1459. {
  1460. #ifndef _GAMECONSOLE
  1461. gamestats->Event_CrateSmashed();
  1462. #endif
  1463. }
  1464. }
  1465. IGameEvent * event = gameeventmanager->CreateEvent( "break_prop", false, &g_BreakPropEvent );
  1466. if ( event )
  1467. {
  1468. if ( pBreaker && pBreaker->IsPlayer() )
  1469. {
  1470. event->SetInt( "userid", ToBasePlayer( pBreaker )->GetUserID() );
  1471. }
  1472. else
  1473. {
  1474. event->SetInt( "userid", 0 );
  1475. }
  1476. event->SetInt( "entindex", entindex() );
  1477. gameeventmanager->FireEvent( event );
  1478. }
  1479. m_takedamage = DAMAGE_NO;
  1480. m_OnBreak.FireOutput( pBreaker, this );
  1481. Vector velocity;
  1482. AngularImpulse angVelocity;
  1483. IPhysicsObject *pPhysics = GetRootPhysicsObjectForBreak();
  1484. Vector origin;
  1485. QAngle angles;
  1486. AddSolidFlags( FSOLID_NOT_SOLID );
  1487. if ( pPhysics )
  1488. {
  1489. pPhysics->GetVelocity( &velocity, &angVelocity );
  1490. pPhysics->GetPosition( &origin, &angles );
  1491. pPhysics->RecheckCollisionFilter();
  1492. }
  1493. else
  1494. {
  1495. velocity = GetAbsVelocity();
  1496. QAngleToAngularImpulse( GetLocalAngularVelocity(), angVelocity );
  1497. origin = GetAbsOrigin();
  1498. angles = GetAbsAngles();
  1499. }
  1500. PhysBreakSound( this, VPhysicsGetObject(), GetAbsOrigin() );
  1501. bool bExploded = false;
  1502. CBaseEntity *pAttacker = info.GetAttacker();
  1503. if ( m_hLastAttacker )
  1504. {
  1505. // Pass along the person who made this explosive breakable explode.
  1506. // This way the player allies can get immunity from barrels exploded by the player.
  1507. pAttacker = m_hLastAttacker;
  1508. }
  1509. else if( m_hPhysicsAttacker )
  1510. {
  1511. // If I have a physics attacker and was influenced in the last 2 seconds,
  1512. // Make the attacker my physics attacker. This helps protect citizens from dying
  1513. // in the explosion of a physics object that was thrown by the player's physgun
  1514. // and exploded on impact.
  1515. if( gpGlobals->curtime - m_flLastPhysicsInfluenceTime <= 2.0f )
  1516. {
  1517. pAttacker = m_hPhysicsAttacker;
  1518. }
  1519. }
  1520. if ( m_explodeDamage > 0 || m_explodeRadius > 0 )
  1521. {
  1522. if( HasInteraction( PROPINTER_PHYSGUN_BREAK_EXPLODE ) )
  1523. {
  1524. ExplosionCreate( WorldSpaceCenter(), angles, pAttacker, m_explodeDamage, m_explodeRadius,
  1525. SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE | SF_ENVEXPLOSION_SURFACEONLY | SF_ENVEXPLOSION_NOSOUND,
  1526. 0.0f, this );
  1527. EmitSound("PropaneTank.Burst");
  1528. }
  1529. else if( HasInteraction( PROPINTER_PHYSGUN_BREAK_EXPLODE_ICE ) )
  1530. {
  1531. ExplosionCreate( WorldSpaceCenter(), angles, pAttacker, m_explodeDamage, m_explodeRadius,
  1532. SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE | SF_ENVEXPLOSION_SURFACEONLY | SF_ENVEXPLOSION_NOSOUND | SF_ENVEXPLOSION_ICE,
  1533. 0.0f, this );
  1534. EmitSound("PropaneTank.Burst");
  1535. }
  1536. else
  1537. {
  1538. #ifdef PORTAL2
  1539. float flScale = GetModelHierarchyScale();
  1540. #else
  1541. float flScale = 1.0f;
  1542. #endif // PORTAL2
  1543. ExplosionCreate( WorldSpaceCenter(), angles, pAttacker, m_explodeDamage * flScale, m_explodeRadius * flScale,
  1544. SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE | SF_ENVEXPLOSION_SURFACEONLY,
  1545. 0.0f, this );
  1546. }
  1547. bExploded = true;
  1548. }
  1549. // Allow derived classes to emit special things
  1550. OnBreak( velocity, angVelocity, pBreaker );
  1551. breakablepropparams_t params( origin, angles, velocity, angVelocity );
  1552. params.impactEnergyScale = m_impactEnergyScale;
  1553. params.defCollisionGroup = GetCollisionGroup();
  1554. if ( params.defCollisionGroup == COLLISION_GROUP_NONE )
  1555. {
  1556. // don't automatically make anything COLLISION_GROUP_NONE or it will
  1557. // collide with debris being ejected by breaking
  1558. params.defCollisionGroup = COLLISION_GROUP_INTERACTIVE;
  1559. }
  1560. params.defBurstScale = 100;
  1561. // in multiplayer spawn break models as clientside temp ents
  1562. if ( gpGlobals->maxClients > 1 && breakable_multiplayer.GetBool() )
  1563. {
  1564. CPASFilter filter( WorldSpaceCenter() );
  1565. Vector velocity; velocity.Init();
  1566. if ( pPhysics )
  1567. pPhysics->GetVelocity( &velocity, NULL );
  1568. switch ( GetMultiplayerBreakMode() )
  1569. {
  1570. case MULTIPLAYER_BREAK_DEFAULT: // default is to break client-side
  1571. case MULTIPLAYER_BREAK_CLIENTSIDE:
  1572. te->PhysicsProp( filter, -1, GetModelIndex(), m_nSkin, GetAbsOrigin(), GetAbsAngles(), velocity, true, GetEffects(), GetRenderColor() );
  1573. break;
  1574. case MULTIPLAYER_BREAK_SERVERSIDE: // server-side break
  1575. if ( m_PerformanceMode != PM_NO_GIBS || breakable_disable_gib_limit.GetBool() )
  1576. {
  1577. PropBreakableCreateAll( GetModelIndex(), pPhysics, params, this, -1, ( m_PerformanceMode == PM_FULL_GIBS ), false );
  1578. }
  1579. break;
  1580. case MULTIPLAYER_BREAK_BOTH: // pieces break from both dlls
  1581. te->PhysicsProp( filter, -1, GetModelIndex(), m_nSkin, GetAbsOrigin(), GetAbsAngles(), velocity, true, GetEffects(), GetRenderColor() );
  1582. if ( m_PerformanceMode != PM_NO_GIBS || breakable_disable_gib_limit.GetBool() )
  1583. {
  1584. PropBreakableCreateAll( GetModelIndex(), pPhysics, params, this, -1, ( m_PerformanceMode == PM_FULL_GIBS ), false );
  1585. }
  1586. break;
  1587. }
  1588. }
  1589. // no damage/damage force? set a burst of 100 for some movement
  1590. else if ( m_PerformanceMode != PM_NO_GIBS || breakable_disable_gib_limit.GetBool() )
  1591. {
  1592. PropBreakableCreateAll( GetModelIndex(), pPhysics, params, this, -1, ( m_PerformanceMode == PM_FULL_GIBS ) );
  1593. }
  1594. if( HasInteraction( PROPINTER_PHYSGUN_BREAK_EXPLODE ) )
  1595. {
  1596. if ( bExploded == false )
  1597. {
  1598. ExplosionCreate( origin, angles, pAttacker, 1, m_explodeRadius,
  1599. SF_ENVEXPLOSION_NODAMAGE | SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE, 0.0f, this );
  1600. }
  1601. // Find and ignite all NPC's within the radius
  1602. CBaseEntity *pEntity = NULL;
  1603. for ( CEntitySphereQuery sphere( origin, m_explodeRadius ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
  1604. {
  1605. if( pEntity && pEntity->MyCombatCharacterPointer() )
  1606. {
  1607. // Check damage filters so we don't ignite friendlies
  1608. if ( pEntity->PassesDamageFilter( info ) )
  1609. {
  1610. pEntity->MyCombatCharacterPointer()->Ignite( 30 );
  1611. }
  1612. }
  1613. }
  1614. }
  1615. if( HasInteraction( PROPINTER_PHYSGUN_BREAK_EXPLODE_ICE ) )
  1616. {
  1617. if ( bExploded == false )
  1618. {
  1619. ExplosionCreate( origin, angles, pAttacker, 1, m_explodeRadius,
  1620. SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE | SF_ENVEXPLOSION_ICE, 0.0f, this );
  1621. }
  1622. // Find and freeze all NPC's within the radius
  1623. CBaseEntity *pEntity = NULL;
  1624. for ( CEntitySphereQuery sphere( origin, m_explodeRadius ); ( pEntity = sphere.GetCurrentEntity() ) != NULL; sphere.NextEntity() )
  1625. {
  1626. if( pEntity && pEntity->MyCombatCharacterPointer() )
  1627. {
  1628. // Check damage filters so we don't ignite friendlies
  1629. if ( pEntity->PassesDamageFilter( info ) )
  1630. {
  1631. CAI_BaseNPC *pNPC = dynamic_cast<CAI_BaseNPC*>( pEntity );
  1632. if ( pNPC )
  1633. {
  1634. pNPC->Freeze( 4.0f, pAttacker );
  1635. }
  1636. }
  1637. }
  1638. }
  1639. }
  1640. UTIL_Remove( this );
  1641. }
  1642. //=============================================================================================================
  1643. // DYNAMIC PROPS
  1644. //=============================================================================================================
  1645. #ifndef INFESTED_DLL
  1646. LINK_ENTITY_TO_CLASS( dynamic_prop, CDynamicProp );
  1647. LINK_ENTITY_TO_CLASS( prop_dynamic, CDynamicProp );
  1648. LINK_ENTITY_TO_CLASS( prop_dynamic_override, CDynamicProp );
  1649. LINK_ENTITY_TO_CLASS( prop_dynamic_glow, CDynamicProp );
  1650. #endif
  1651. BEGIN_DATADESC( CDynamicProp )
  1652. // Fields
  1653. DEFINE_KEYFIELD( m_iszDefaultAnim, FIELD_STRING, "DefaultAnim"),
  1654. DEFINE_FIELD( m_iGoalSequence, FIELD_INTEGER ),
  1655. DEFINE_FIELD( m_iTransitionDirection, FIELD_INTEGER ),
  1656. DEFINE_KEYFIELD( m_bRandomAnimator, FIELD_BOOLEAN, "RandomAnimation"),
  1657. DEFINE_FIELD( m_flNextRandAnim, FIELD_TIME ),
  1658. DEFINE_KEYFIELD( m_flMinRandAnimTime, FIELD_FLOAT, "MinAnimTime"),
  1659. DEFINE_KEYFIELD( m_flMaxRandAnimTime, FIELD_FLOAT, "MaxAnimTime"),
  1660. DEFINE_KEYFIELD( m_bStartDisabled, FIELD_BOOLEAN, "StartDisabled" ),
  1661. DEFINE_FIELD( m_bUseHitboxesForRenderBox, FIELD_BOOLEAN ),
  1662. DEFINE_FIELD( m_nPendingSequence, FIELD_SHORT ),
  1663. DEFINE_KEYFIELD( m_bDisableBoneFollowers, FIELD_BOOLEAN, "DisableBoneFollowers" ),
  1664. DEFINE_FIELD( m_bAnimationDone, FIELD_BOOLEAN ),
  1665. DEFINE_KEYFIELD( m_bHoldAnimation, FIELD_BOOLEAN, "HoldAnimation" ),
  1666. DEFINE_KEYFIELD( m_bAnimateEveryFrame, FIELD_BOOLEAN, "AnimateEveryFrame" ),
  1667. DEFINE_KEYFIELD( m_flGlowMaxDist, FIELD_FLOAT, "glowdist" ),
  1668. DEFINE_KEYFIELD( m_bShouldGlow, FIELD_BOOLEAN, "glowenabled" ),
  1669. DEFINE_KEYFIELD( m_clrGlow, FIELD_COLOR32, "glowcolor" ),
  1670. DEFINE_KEYFIELD( m_nGlowStyle, FIELD_INTEGER, "glowstyle" ),
  1671. // Inputs
  1672. DEFINE_INPUTFUNC( FIELD_STRING, "SetAnimation", InputSetAnimation ),
  1673. DEFINE_INPUTFUNC( FIELD_STRING, "SetAnimationNoReset", InputSetAnimationNoReset ),
  1674. DEFINE_INPUTFUNC( FIELD_STRING, "SetDefaultAnimation", InputSetDefaultAnimation ),
  1675. DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ),
  1676. DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ),
  1677. DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputTurnOn ),
  1678. DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputTurnOff ),
  1679. DEFINE_INPUTFUNC( FIELD_VOID, "EnableCollision", InputEnableCollision ),
  1680. DEFINE_INPUTFUNC( FIELD_VOID, "DisableCollision", InputDisableCollision ),
  1681. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPlaybackRate", InputSetPlaybackRate ),
  1682. DEFINE_INPUTFUNC( FIELD_VOID, "BecomeRagdoll", InputBecomeRagdoll ),
  1683. DEFINE_INPUTFUNC( FIELD_VOID, "FadeAndKill", InputFadeAndKill ),
  1684. DEFINE_INPUTFUNC( FIELD_VOID, "SetGlowEnabled", InputSetGlowEnabled ),
  1685. DEFINE_INPUTFUNC( FIELD_VOID, "SetGlowDisabled", InputSetGlowDisabled ),
  1686. DEFINE_INPUTFUNC( FIELD_COLOR32, "SetGlowColor", InputSetGlowColor ),
  1687. DEFINE_INPUTFUNC( FIELD_FLOAT, "GlowColorRedValue", InputGlowColorRedValue ),
  1688. DEFINE_INPUTFUNC( FIELD_FLOAT, "GlowColorGreenValue", InputGlowColorGreenValue ),
  1689. DEFINE_INPUTFUNC( FIELD_FLOAT, "GlowColorBlueValue", InputGlowColorBlueValue ),
  1690. // Outputs
  1691. DEFINE_OUTPUT( m_pOutputAnimBegun, "OnAnimationBegun" ),
  1692. DEFINE_OUTPUT( m_pOutputAnimOver, "OnAnimationDone" ),
  1693. // Function Pointers
  1694. DEFINE_THINKFUNC( AnimThink ),
  1695. DEFINE_EMBEDDED( m_BoneFollowerManager ),
  1696. END_DATADESC()
  1697. IMPLEMENT_SERVERCLASS_ST(CDynamicProp, DT_DynamicProp)
  1698. SendPropBool( SENDINFO( m_bUseHitboxesForRenderBox ) ),
  1699. SendPropFloat( SENDINFO( m_flGlowMaxDist ) ),
  1700. SendPropBool( SENDINFO( m_bShouldGlow ) ),
  1701. SendPropInt( SENDINFO(m_clrGlow), 32, SPROP_UNSIGNED, SendProxy_Color32ToInt32 ),
  1702. SendPropInt( SENDINFO( m_nGlowStyle ) ),
  1703. END_SEND_TABLE()
  1704. //-----------------------------------------------------------------------------
  1705. // Purpose:
  1706. //-----------------------------------------------------------------------------
  1707. CDynamicProp::CDynamicProp()
  1708. {
  1709. m_nPendingSequence = -1;
  1710. if ( g_pGameRules->IsMultiplayer() )
  1711. {
  1712. UseClientSideAnimation();
  1713. }
  1714. m_iGoalSequence = -1;
  1715. m_bShouldGlow = false;
  1716. m_clrGlow.Init( 255, 255, 255, 0 );
  1717. //m_nGlowStyle = 0;
  1718. }
  1719. //------------------------------------------------------------------------------
  1720. // Purpose:
  1721. //------------------------------------------------------------------------------
  1722. void CDynamicProp::Spawn( )
  1723. {
  1724. // Condense classname's to one, except for "prop_dynamic_override"
  1725. if ( FClassnameIs( this, "dynamic_prop" ) )
  1726. {
  1727. SetClassname( "prop_dynamic" );
  1728. }
  1729. // If the prop is not-solid, the bounding box needs to be
  1730. // OBB to correctly surround the prop as it rotates.
  1731. // Check the classname so we don't mess with doors & other derived classes.
  1732. if ( GetSolid() == SOLID_NONE && FClassnameIs( this, "prop_dynamic" ) )
  1733. {
  1734. SetSolid( SOLID_OBB );
  1735. AddSolidFlags( FSOLID_NOT_SOLID );
  1736. }
  1737. BaseClass::Spawn();
  1738. #ifdef PORTAL2
  1739. AddFlag( FL_UNPAINTABLE );
  1740. #endif
  1741. if ( IsMarkedForDeletion() )
  1742. return;
  1743. // Now condense all classnames to one
  1744. if ( FClassnameIs( this, "dynamic_prop" ) || FClassnameIs( this, "prop_dynamic_override" ) )
  1745. {
  1746. SetClassname("prop_dynamic");
  1747. }
  1748. AddFlag( FL_STATICPROP );
  1749. if ( m_bRandomAnimator || ( m_iszDefaultAnim != NULL_STRING ) )
  1750. {
  1751. RemoveFlag( FL_STATICPROP );
  1752. if ( m_bRandomAnimator )
  1753. {
  1754. SetThink( &CDynamicProp::AnimThink );
  1755. m_flNextRandAnim = gpGlobals->curtime + random->RandomFloat( m_flMinRandAnimTime, m_flMaxRandAnimTime );
  1756. SetNextThink( gpGlobals->curtime + m_flNextRandAnim + 0.1 );
  1757. }
  1758. else
  1759. {
  1760. PropSetAnim( STRING( m_iszDefaultAnim ) );
  1761. }
  1762. }
  1763. CreateVPhysics();
  1764. BoneFollowerHierarchyChanged();
  1765. if( m_bStartDisabled )
  1766. {
  1767. AddEffects( EF_NODRAW );
  1768. }
  1769. if ( !PropDataOverrodeBlockLOS() )
  1770. {
  1771. CalculateBlockLOS();
  1772. }
  1773. m_bUseHitboxesForRenderBox = HasSpawnFlags( SF_DYNAMICPROP_USEHITBOX_FOR_RENDERBOX );
  1774. if ( HasSpawnFlags( SF_DYNAMICPROP_DISABLE_COLLISION ) )
  1775. {
  1776. AddSolidFlags( FSOLID_NOT_SOLID );
  1777. }
  1778. if( m_bAnimateEveryFrame )
  1779. {
  1780. SetAnimatedEveryTick( true );
  1781. }
  1782. //m_debugOverlays |= OVERLAY_ABSBOX_BIT;
  1783. }
  1784. //-----------------------------------------------------------------------------
  1785. // Purpose:
  1786. //-----------------------------------------------------------------------------
  1787. void CDynamicProp::OnRestore( void )
  1788. {
  1789. BaseClass::OnRestore();
  1790. BoneFollowerHierarchyChanged();
  1791. }
  1792. void CDynamicProp::SetParent( CBaseEntity *pNewParent, int iAttachment )
  1793. {
  1794. BaseClass::SetParent(pNewParent, iAttachment);
  1795. BoneFollowerHierarchyChanged();
  1796. }
  1797. // Call this when creating bone followers or changing hierarchy to make sure the bone followers get updated when hierarchy changes
  1798. void CDynamicProp::BoneFollowerHierarchyChanged()
  1799. {
  1800. // If we have bone followers and we're parented to something, we need to constantly update our bone followers
  1801. if ( m_BoneFollowerManager.GetNumBoneFollowers() && GetParent() )
  1802. {
  1803. WatchPositionChanges(this, this);
  1804. }
  1805. }
  1806. //-----------------------------------------------------------------------------
  1807. // Purpose:
  1808. //-----------------------------------------------------------------------------
  1809. bool CDynamicProp::OverridePropdata( void )
  1810. {
  1811. return ( FClassnameIs(this, "prop_dynamic_override" ) );
  1812. }
  1813. //------------------------------------------------------------------------------
  1814. // Purpose:
  1815. //------------------------------------------------------------------------------
  1816. bool CDynamicProp::CreateVPhysics( void )
  1817. {
  1818. if ( GetSolid() == SOLID_NONE || ((GetSolidFlags() & FSOLID_NOT_SOLID) && HasSpawnFlags(SF_DYNAMICPROP_NO_VPHYSICS)))
  1819. return true;
  1820. if ( m_bDisableBoneFollowers == false )
  1821. {
  1822. CreateBoneFollowers();
  1823. }
  1824. if ( m_BoneFollowerManager.GetNumBoneFollowers() )
  1825. {
  1826. if ( GetSolidFlags() & FSOLID_NOT_SOLID )
  1827. {
  1828. // Already non-solid? Must need bone followers for some other reason
  1829. // like needing to attach constraints to this object
  1830. for ( int i = 0; i < m_BoneFollowerManager.GetNumBoneFollowers(); i++ )
  1831. {
  1832. CBaseEntity *pFollower = m_BoneFollowerManager.GetBoneFollower(i)->hFollower;
  1833. if ( pFollower )
  1834. {
  1835. pFollower->AddSolidFlags(FSOLID_NOT_SOLID);
  1836. }
  1837. }
  1838. }
  1839. // If our collision is through bone followers, we want to be non-solid
  1840. AddSolidFlags( FSOLID_NOT_SOLID );
  1841. // add these for the client, FSOLID_NOT_SOLID should keep it out of the testCollision code
  1842. // except in the case of TraceEntity() which the client does for impact effects
  1843. AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST );
  1844. return true;
  1845. }
  1846. else
  1847. {
  1848. VPhysicsInitStatic();
  1849. }
  1850. return true;
  1851. }
  1852. void CDynamicProp::CreateBoneFollowers()
  1853. {
  1854. // already created bone followers? Don't do so again.
  1855. if ( m_BoneFollowerManager.GetNumBoneFollowers() )
  1856. return;
  1857. KeyValues *pModelKV = modelinfo->GetModelKeyValues( GetModel() );
  1858. if ( pModelKV )
  1859. {
  1860. // Do we have a bone follower section?
  1861. KeyValues *pkvBoneFollowers = pModelKV->FindKey("bone_followers");
  1862. if ( pkvBoneFollowers )
  1863. {
  1864. // Loop through the list and create the bone followers
  1865. KeyValues *pBone = pkvBoneFollowers->GetFirstSubKey();
  1866. while ( pBone )
  1867. {
  1868. // Add it to the list
  1869. const char *pBoneName = pBone->GetString();
  1870. m_BoneFollowerManager.AddBoneFollower( this, pBoneName );
  1871. pBone = pBone->GetNextKey();
  1872. }
  1873. }
  1874. }
  1875. // if we got here, we don't have a bone follower section, but if we have a ragdoll
  1876. // go ahead and create default bone followers for it
  1877. if ( m_BoneFollowerManager.GetNumBoneFollowers() == 0 )
  1878. {
  1879. vcollide_t *pCollide = modelinfo->GetVCollide( GetModelIndex() );
  1880. if ( pCollide && pCollide->solidCount > 1 )
  1881. {
  1882. CreateBoneFollowersFromRagdoll(this, &m_BoneFollowerManager, pCollide);
  1883. }
  1884. }
  1885. }
  1886. bool CDynamicProp::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
  1887. {
  1888. if ( IsSolidFlagSet(FSOLID_NOT_SOLID) )
  1889. {
  1890. // if this entity is marked non-solid and custom test it must have bone followers
  1891. if ( IsSolidFlagSet( FSOLID_CUSTOMBOXTEST ) && IsSolidFlagSet( FSOLID_CUSTOMRAYTEST ))
  1892. {
  1893. for ( int i = 0; i < m_BoneFollowerManager.GetNumBoneFollowers(); i++ )
  1894. {
  1895. CBaseEntity *pEntity = m_BoneFollowerManager.GetBoneFollower(i)->hFollower;
  1896. if ( pEntity && pEntity->TestCollision(ray, mask, trace) )
  1897. return true;
  1898. }
  1899. }
  1900. }
  1901. // PORTAL2: This is a change from shipped code, but should be benign
  1902. return BaseClass::TestCollision( ray, mask, trace );
  1903. }
  1904. IPhysicsObject *CDynamicProp::GetRootPhysicsObjectForBreak()
  1905. {
  1906. if ( m_BoneFollowerManager.GetNumBoneFollowers() )
  1907. {
  1908. physfollower_t *pFollower = m_BoneFollowerManager.GetBoneFollower(0);
  1909. CBaseEntity *pFollowerEntity = pFollower->hFollower;
  1910. if ( pFollowerEntity )
  1911. {
  1912. return pFollowerEntity->VPhysicsGetObject();
  1913. }
  1914. }
  1915. return BaseClass::GetRootPhysicsObjectForBreak();
  1916. }
  1917. //-----------------------------------------------------------------------------
  1918. // Purpose:
  1919. //-----------------------------------------------------------------------------
  1920. void CDynamicProp::UpdateOnRemove( void )
  1921. {
  1922. m_BoneFollowerManager.DestroyBoneFollowers();
  1923. BaseClass::UpdateOnRemove();
  1924. }
  1925. //-----------------------------------------------------------------------------
  1926. //-----------------------------------------------------------------------------
  1927. void CDynamicProp::HandleAnimEvent( animevent_t *pEvent )
  1928. {
  1929. switch( pEvent->Event() )
  1930. {
  1931. case SCRIPT_EVENT_FIRE_INPUT:
  1932. {
  1933. variant_t emptyVariant;
  1934. this->AcceptInput( pEvent->options, this, this, emptyVariant, 0 );
  1935. return;
  1936. }
  1937. case SCRIPT_EVENT_SOUND:
  1938. {
  1939. EmitSound( pEvent->options );
  1940. break;
  1941. }
  1942. default:
  1943. {
  1944. break;
  1945. }
  1946. }
  1947. BaseClass::HandleAnimEvent( pEvent );
  1948. }
  1949. int CDynamicProp::ShouldTransmit( const CCheckTransmitInfo *pInfo )
  1950. {
  1951. // If we're glowing through walls, we need to always transmit
  1952. if ( m_bShouldGlow )
  1953. return FL_EDICT_ALWAYS;
  1954. return BaseClass::ShouldTransmit( pInfo );
  1955. }
  1956. //-----------------------------------------------------------------------------
  1957. // Purpose:
  1958. //-----------------------------------------------------------------------------
  1959. void CDynamicProp::NotifyPositionChanged( CBaseEntity *pEntity )
  1960. {
  1961. Assert(pEntity==this);
  1962. UpdateBoneFollowers();
  1963. }
  1964. //------------------------------------------------------------------------------
  1965. // Purpose:
  1966. //------------------------------------------------------------------------------
  1967. void CDynamicProp::AnimThink( void )
  1968. {
  1969. if ( m_nPendingSequence != -1 )
  1970. {
  1971. FinishSetSequence( m_nPendingSequence );
  1972. m_nPendingSequence = -1;
  1973. }
  1974. if ( m_bRandomAnimator && m_flNextRandAnim < gpGlobals->curtime )
  1975. {
  1976. ResetSequence( SelectWeightedSequence( ACT_IDLE ) );
  1977. ResetClientsideFrame();
  1978. // Fire output
  1979. m_pOutputAnimBegun.FireOutput( NULL,this );
  1980. m_flNextRandAnim = gpGlobals->curtime + random->RandomFloat( m_flMinRandAnimTime, m_flMaxRandAnimTime );
  1981. }
  1982. float flPlaybackRate = GetPlaybackRate();
  1983. bool bPlayingForward = (flPlaybackRate >= 0.0f);
  1984. // if transition is negative, assert that playbackrate is also negative
  1985. Assert( ((m_iTransitionDirection > 0) && (flPlaybackRate >= 0)) || ((m_iTransitionDirection < 0) && (flPlaybackRate <= 0)) );
  1986. bool bPropFinished = ((bPlayingForward && GetCycle() >= 0.999f) || (!bPlayingForward && GetCycle() <= 0.0f)) && !SequenceLoops();
  1987. if ( bPropFinished )
  1988. {
  1989. Assert( m_iGoalSequence >= 0 );
  1990. if (GetSequence() != m_iGoalSequence)
  1991. {
  1992. PropSetSequence( m_iGoalSequence );
  1993. bPropFinished = false;
  1994. }
  1995. else
  1996. {
  1997. // Fire output
  1998. if ( !m_bAnimationDone )
  1999. {
  2000. m_bAnimationDone = true;
  2001. m_pOutputAnimOver.FireOutput(NULL,this);
  2002. }
  2003. // If I'm a random animator, think again when it's time to change sequence
  2004. if ( m_bRandomAnimator )
  2005. {
  2006. SetNextThink( gpGlobals->curtime + m_flNextRandAnim + 0.1 );
  2007. }
  2008. else
  2009. {
  2010. if ( m_iszDefaultAnim != NULL_STRING && m_bHoldAnimation == false )
  2011. {
  2012. PropSetAnim( STRING( m_iszDefaultAnim ) );
  2013. bPropFinished = false;
  2014. }
  2015. // We need to wait for an animation change to come in
  2016. if ( m_bHoldAnimation )
  2017. {
  2018. SetNextThink( gpGlobals->curtime + 0.1f );
  2019. }
  2020. }
  2021. }
  2022. }
  2023. else
  2024. {
  2025. m_bAnimationDone = false;
  2026. SetNextThink( gpGlobals->curtime + 0.1f );
  2027. }
  2028. if( m_bAnimateEveryFrame )
  2029. {
  2030. SetNextThink( gpGlobals->curtime );
  2031. }
  2032. // if we've already stopped animating and have nothing left to do, skip the rest
  2033. if ( bPropFinished && m_nPendingSequence == -1 && IsSequenceFinished() )
  2034. {
  2035. // DevMsg("%6.2f (%d) : paused\n", gpGlobals->curtime, entindex() );
  2036. return;
  2037. }
  2038. StudioFrameAdvance();
  2039. DispatchAnimEvents(this);
  2040. UpdateBoneFollowers();
  2041. // Queue any SetParentAttached children to update at the end of the frame
  2042. for ( CBaseEntity *pChild = FirstMoveChild(); pChild; pChild = pChild->NextMovePeer() )
  2043. {
  2044. g_pPushedEntities->QueueChildUpdate( pChild );
  2045. }
  2046. }
  2047. //------------------------------------------------------------------------------
  2048. // Purpose: Cause our bone followers to follow us
  2049. //------------------------------------------------------------------------------
  2050. void CDynamicProp::UpdateBoneFollowers( void )
  2051. {
  2052. m_BoneFollowerManager.UpdateBoneFollowers( this );
  2053. }
  2054. //------------------------------------------------------------------------------
  2055. // Purpose: Sets an animation by sequence name or activity name.
  2056. //------------------------------------------------------------------------------
  2057. void CDynamicProp::PropSetAnim( const char *szAnim )
  2058. {
  2059. if ( !szAnim )
  2060. return;
  2061. // short circuit looking up sequence name
  2062. int nSequence = GetSequence();
  2063. if ( V_stricmp( szAnim, GetSequenceName( nSequence ) ) )
  2064. {
  2065. nSequence = LookupSequence( szAnim );
  2066. }
  2067. // Set to the desired anim, or default anim if the desired is not present
  2068. if ( nSequence > ACTIVITY_NOT_AVAILABLE )
  2069. {
  2070. PropSetSequence( nSequence );
  2071. // Fire output
  2072. m_pOutputAnimBegun.FireOutput( NULL,this );
  2073. }
  2074. else
  2075. {
  2076. // Not available try to get default anim
  2077. Warning( "Dynamic prop %s: no sequence named:%s\n", GetDebugName(), szAnim );
  2078. SetSequence( 0 );
  2079. }
  2080. }
  2081. inline void CDynamicProp::SetGlowColor( int r, int g, int b )
  2082. {
  2083. m_clrGlow.SetR( r );
  2084. m_clrGlow.SetG( g );
  2085. m_clrGlow.SetB( b );
  2086. }
  2087. inline const color24 CDynamicProp::GetGlowColor() const
  2088. {
  2089. color24 c = { m_clrGlow->r, m_clrGlow->g, m_clrGlow->b };
  2090. return c;
  2091. }
  2092. //------------------------------------------------------------------------------
  2093. // Purpose:
  2094. //------------------------------------------------------------------------------
  2095. void CDynamicProp::InputSetAnimation( inputdata_t &inputdata )
  2096. {
  2097. PropSetAnim( inputdata.value.String() );
  2098. }
  2099. //------------------------------------------------------------------------------
  2100. // Purpose: Set the animation unless the prop is already set to this particular animation
  2101. //------------------------------------------------------------------------------
  2102. void CDynamicProp::InputSetAnimationNoReset( inputdata_t &inputdata )
  2103. {
  2104. if ( GetSequence() != LookupSequence( inputdata.value.String() ) )
  2105. {
  2106. PropSetAnim( inputdata.value.String() );
  2107. }
  2108. }
  2109. //------------------------------------------------------------------------------
  2110. // Purpose:
  2111. //------------------------------------------------------------------------------
  2112. void CDynamicProp::InputSetDefaultAnimation( inputdata_t &inputdata )
  2113. {
  2114. m_iszDefaultAnim = inputdata.value.StringID();
  2115. }
  2116. //-----------------------------------------------------------------------------
  2117. // Purpose:
  2118. //-----------------------------------------------------------------------------
  2119. void CDynamicProp::InputSetPlaybackRate( inputdata_t &inputdata )
  2120. {
  2121. float flPlaybackRate = inputdata.value.Float();
  2122. if ( GetPlaybackRate() != flPlaybackRate )
  2123. {
  2124. SetPlaybackRate( flPlaybackRate );
  2125. if ( GetNextThink() <= gpGlobals->curtime )
  2126. {
  2127. SetThink( &CDynamicProp::AnimThink );
  2128. SetNextThink( gpGlobals->curtime );
  2129. }
  2130. }
  2131. }
  2132. //-----------------------------------------------------------------------------
  2133. // Purpose: Helper in case we have to async load the sequence
  2134. // Input : nSequence -
  2135. //-----------------------------------------------------------------------------
  2136. void CDynamicProp::FinishSetSequence( int nSequence )
  2137. {
  2138. // Msg("%.2f CDynamicProp::FinishSetSequence( %d )\n", gpGlobals->curtime, nSequence );
  2139. SetCycle( 0 );
  2140. m_flAnimTime = gpGlobals->curtime;
  2141. ResetSequence( nSequence );
  2142. ResetClientsideFrame();
  2143. RemoveFlag( FL_STATICPROP );
  2144. SetPlaybackRate( m_iTransitionDirection > 0 ? 1.0f : -1.0f );
  2145. SetCycle( m_iTransitionDirection > 0 ? 0.0f : 0.999f );
  2146. }
  2147. //-----------------------------------------------------------------------------
  2148. // Purpose: Sets the sequence and starts thinking.
  2149. // Input : nSequence -
  2150. //-----------------------------------------------------------------------------
  2151. void CDynamicProp::PropSetSequence( int nSequence )
  2152. {
  2153. m_iGoalSequence = nSequence;
  2154. // Msg("%.2f CDynamicProp::PropSetSequence( %d (%d:%.1f:%.3f)\n", gpGlobals->curtime, nSequence, GetSequence(), GetPlaybackRate(), GetCycle() );
  2155. int nNextSequence;
  2156. float nextCycle;
  2157. float flInterval = 0.1f;
  2158. if (GotoSequence( GetSequence(), GetCycle(), GetPlaybackRate(), m_iGoalSequence, nNextSequence, nextCycle, m_iTransitionDirection ))
  2159. {
  2160. FinishSetSequence( nNextSequence );
  2161. }
  2162. SetThink( &CDynamicProp::AnimThink );
  2163. if ( GetNextThink() <= gpGlobals->curtime )
  2164. SetNextThink( gpGlobals->curtime + flInterval );
  2165. }
  2166. // NOTE: To avoid risk, currently these do nothing about collisions, only visually on/off
  2167. void CDynamicProp::InputTurnOn( inputdata_t &inputdata )
  2168. {
  2169. RemoveEffects( EF_NODRAW );
  2170. }
  2171. void CDynamicProp::InputTurnOff( inputdata_t &inputdata )
  2172. {
  2173. AddEffects( EF_NODRAW );
  2174. }
  2175. void CDynamicProp::InputDisableCollision( inputdata_t &inputdata )
  2176. {
  2177. AddSolidFlags( FSOLID_NOT_SOLID );
  2178. }
  2179. void CDynamicProp::InputEnableCollision( inputdata_t &inputdata )
  2180. {
  2181. RemoveSolidFlags( FSOLID_NOT_SOLID );
  2182. }
  2183. //-----------------------------------------------------------------------------
  2184. // Purpose:
  2185. //-----------------------------------------------------------------------------
  2186. void CDynamicProp::InputBecomeRagdoll( inputdata_t &inputdata )
  2187. {
  2188. BecomeRagdollOnClient( vec3_origin );
  2189. }
  2190. void CDynamicProp::InputFadeAndKill( inputdata_t &inputdata )
  2191. {
  2192. SUB_StartFadeOutInstant();
  2193. }
  2194. void CDynamicProp::InputSetGlowEnabled( inputdata_t &inputdata )
  2195. {
  2196. m_bShouldGlow = true;
  2197. }
  2198. void CDynamicProp::InputSetGlowDisabled( inputdata_t &inputdata )
  2199. {
  2200. m_bShouldGlow = false;
  2201. CReliableBroadcastRecipientFilter filter;
  2202. filter.MakeReliable();
  2203. CCSUsrMsg_GlowPropTurnOff msg;
  2204. msg.set_entidx( entindex() ); // this prop
  2205. SendUserMessage( filter, CS_UM_GlowPropTurnOff, msg );
  2206. }
  2207. void CDynamicProp::InputSetGlowColor(inputdata_t &inputdata)
  2208. {
  2209. color32 color = inputdata.value.Color32();
  2210. SetGlowColor( color.r, color.g, color.b );
  2211. }
  2212. void CDynamicProp::InputGlowColorRedValue( inputdata_t &inputdata )
  2213. {
  2214. int nNewColor = clamp( inputdata.value.Float(), 0, 255 );
  2215. SetGlowColor( nNewColor, m_clrGlow->g, m_clrGlow->b );
  2216. }
  2217. void CDynamicProp::InputGlowColorGreenValue( inputdata_t &inputdata )
  2218. {
  2219. int nNewColor = clamp( inputdata.value.Float(), 0, 255 );
  2220. SetGlowColor( m_clrGlow->r, nNewColor, m_clrGlow->b );
  2221. }
  2222. void CDynamicProp::InputGlowColorBlueValue( inputdata_t &inputdata )
  2223. {
  2224. int nNewColor = clamp( inputdata.value.Float(), 0, 255 );
  2225. SetGlowColor( m_clrGlow->r, m_clrGlow->g, nNewColor );
  2226. }
  2227. //-----------------------------------------------------------------------------
  2228. // Purpose: Ornamental prop that follows a studio
  2229. //-----------------------------------------------------------------------------
  2230. class COrnamentProp : public CDynamicProp
  2231. {
  2232. DECLARE_CLASS( COrnamentProp, CDynamicProp );
  2233. public:
  2234. DECLARE_DATADESC();
  2235. void Spawn();
  2236. void Activate();
  2237. void AttachTo( const char *pAttachEntity, CBaseEntity *pActivator = NULL, CBaseEntity *pCaller = NULL );
  2238. void DetachFromOwner();
  2239. // Input handlers
  2240. void InputSetAttached( inputdata_t &inputdata );
  2241. void InputDetach( inputdata_t &inputdata );
  2242. private:
  2243. string_t m_initialOwner;
  2244. };
  2245. LINK_ENTITY_TO_CLASS( prop_dynamic_ornament, COrnamentProp );
  2246. BEGIN_DATADESC( COrnamentProp )
  2247. DEFINE_KEYFIELD( m_initialOwner, FIELD_STRING, "InitialOwner" ),
  2248. // Inputs
  2249. DEFINE_INPUTFUNC( FIELD_STRING, "SetAttached", InputSetAttached ),
  2250. DEFINE_INPUTFUNC( FIELD_VOID, "Detach", InputDetach ),
  2251. END_DATADESC()
  2252. void COrnamentProp::Spawn()
  2253. {
  2254. BaseClass::Spawn();
  2255. DetachFromOwner();
  2256. }
  2257. void COrnamentProp::DetachFromOwner()
  2258. {
  2259. SetOwnerEntity( NULL );
  2260. AddSolidFlags( FSOLID_NOT_SOLID );
  2261. SetMoveType( MOVETYPE_NONE );
  2262. AddEffects( EF_NODRAW );
  2263. }
  2264. void COrnamentProp::Activate()
  2265. {
  2266. BaseClass::Activate();
  2267. if ( m_initialOwner != NULL_STRING )
  2268. {
  2269. AttachTo( STRING(m_initialOwner) );
  2270. }
  2271. }
  2272. void COrnamentProp::InputSetAttached( inputdata_t &inputdata )
  2273. {
  2274. AttachTo( inputdata.value.String(), inputdata.pActivator, inputdata.pCaller );
  2275. }
  2276. void COrnamentProp::AttachTo( const char *pAttachName, CBaseEntity *pActivator, CBaseEntity *pCaller )
  2277. {
  2278. // find and notify the new parent
  2279. CBaseEntity *pAttach = gEntList.FindEntityByName( NULL, pAttachName, NULL, pActivator, pCaller );
  2280. if ( pAttach )
  2281. {
  2282. RemoveEffects( EF_NODRAW );
  2283. FollowEntity( pAttach );
  2284. }
  2285. }
  2286. void COrnamentProp::InputDetach( inputdata_t &inputdata )
  2287. {
  2288. DetachFromOwner();
  2289. }
  2290. //=============================================================================
  2291. // PHYSICS PROPS
  2292. //=============================================================================
  2293. #ifndef INFESTED_DLL
  2294. LINK_ENTITY_TO_CLASS( physics_prop, CPhysicsProp );
  2295. LINK_ENTITY_TO_CLASS( prop_physics, CPhysicsProp );
  2296. LINK_ENTITY_TO_CLASS( prop_physics_override, CPhysicsProp );
  2297. #endif
  2298. BEGIN_DATADESC( CPhysicsProp )
  2299. DEFINE_INPUTFUNC( FIELD_VOID, "EnableMotion", InputEnableMotion ),
  2300. DEFINE_INPUTFUNC( FIELD_VOID, "DisableMotion", InputDisableMotion ),
  2301. DEFINE_INPUTFUNC( FIELD_VOID, "Wake", InputWake ),
  2302. DEFINE_INPUTFUNC( FIELD_VOID, "Sleep", InputSleep ),
  2303. DEFINE_INPUTFUNC( FIELD_VOID, "DisableFloating", InputDisableFloating ),
  2304. DEFINE_FIELD( m_bAwake, FIELD_BOOLEAN ),
  2305. DEFINE_KEYFIELD( m_massScale, FIELD_FLOAT, "massscale" ),
  2306. DEFINE_KEYFIELD( m_inertiaScale, FIELD_FLOAT, "inertiascale" ),
  2307. DEFINE_KEYFIELD( m_damageType, FIELD_INTEGER, "Damagetype" ),
  2308. DEFINE_KEYFIELD( m_iszOverrideScript, FIELD_STRING, "overridescript" ),
  2309. #ifdef PORTAL2
  2310. DEFINE_KEYFIELD( m_bAllowPortalFunnel, FIELD_BOOLEAN, "allowfunnel" ),
  2311. #endif // PORTAL2
  2312. DEFINE_KEYFIELD( m_damageToEnableMotion, FIELD_INTEGER, "damagetoenablemotion" ),
  2313. DEFINE_KEYFIELD( m_flForceToEnableMotion, FIELD_FLOAT, "forcetoenablemotion" ),
  2314. DEFINE_OUTPUT( m_OnAwakened, "OnAwakened" ),
  2315. DEFINE_OUTPUT( m_MotionEnabled, "OnMotionEnabled" ),
  2316. DEFINE_OUTPUT( m_OnPhysGunPickup, "OnPhysGunPickup" ),
  2317. DEFINE_OUTPUT( m_OnPhysGunOnlyPickup, "OnPhysGunOnlyPickup" ),
  2318. DEFINE_OUTPUT( m_OnPhysGunPunt, "OnPhysGunPunt" ),
  2319. DEFINE_OUTPUT( m_OnPhysGunDrop, "OnPhysGunDrop" ),
  2320. DEFINE_OUTPUT( m_OnPlayerUse, "OnPlayerUse" ),
  2321. DEFINE_OUTPUT( m_OnPlayerPickup, "OnPlayerPickup" ),
  2322. DEFINE_OUTPUT( m_OnOutOfWorld, "OnOutOfWorld" ),
  2323. DEFINE_FIELD( m_bThrownByPlayer, FIELD_BOOLEAN ),
  2324. DEFINE_FIELD( m_bFirstCollisionAfterLaunch, FIELD_BOOLEAN ),
  2325. DEFINE_KEYFIELD( m_iExploitableByPlayer, FIELD_INTEGER, "ExploitableByPlayer" ),
  2326. DEFINE_THINKFUNC( ClearFlagsThink ),
  2327. END_DATADESC()
  2328. IMPLEMENT_SERVERCLASS_ST( CPhysicsProp, DT_PhysicsProp )
  2329. //--------------------------------------------------------------------------------------------------------
  2330. // Datatable reduction
  2331. SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
  2332. SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ),
  2333. //SendPropExclude( "DT_BaseAnimating", "m_nSequence" ),
  2334. //SendPropExclude( "DT_BaseAnimating", "m_nNewSequenceParity" ),
  2335. //SendPropExclude( "DT_BaseAnimating", "m_nResetEventsParity" ),
  2336. SendPropExclude( "DT_BaseAnimating", "m_nMuzzleFlashParity" ),
  2337. //SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
  2338. SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ),
  2339. SendPropExclude( "DT_BaseFlex", "m_flexWeight" ),
  2340. SendPropExclude( "DT_BaseFlex", "m_blinktoggle" ),
  2341. // calc mins/maxs on the client, since we have all the info
  2342. //SendPropExclude( "DT_CollisionProperty", "m_vecMins" ),
  2343. //SendPropExclude( "DT_CollisionProperty", "m_vecMaxs" ),
  2344. //SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
  2345. #ifdef TERROR
  2346. SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ),
  2347. #endif
  2348. //--------------------------------------------------------------------------------------------------------
  2349. SendPropBool( SENDINFO( m_bAwake ) ),
  2350. //SendPropInt( SENDINFO(m_spawnflags), 16, SPROP_UNSIGNED ), // Undone: L4D didn't need these bits, but other games do!
  2351. END_SEND_TABLE()
  2352. // external function to tell if this entity is a gib physics prop
  2353. bool PropIsGib( CBaseEntity *pEntity )
  2354. {
  2355. if ( FClassnameIs(pEntity, "prop_physics") )
  2356. {
  2357. CPhysicsProp *pProp = static_cast<CPhysicsProp *>(pEntity);
  2358. return pProp->IsGib();
  2359. }
  2360. return false;
  2361. }
  2362. CPhysicsProp::CPhysicsProp( void ) :
  2363. m_bHasBeenAwakened( false ),
  2364. m_fNextCheckDisableMotionContactsTime( 0 )
  2365. {
  2366. #ifdef PORTAL2
  2367. m_bAllowPortalFunnel = true;
  2368. #endif // PORTAL2
  2369. }
  2370. CPhysicsProp::~CPhysicsProp()
  2371. {
  2372. TheNavMesh->UnregisterAvoidanceObstacle( this );
  2373. if (HasSpawnFlags(SF_PHYSPROP_IS_GIB))
  2374. {
  2375. g_ActiveGibCount--;
  2376. }
  2377. }
  2378. bool CPhysicsProp::IsGib()
  2379. {
  2380. return (m_spawnflags & SF_PHYSPROP_IS_GIB) ? true : false;
  2381. }
  2382. //-----------------------------------------------------------------------------
  2383. // Purpose: Create a physics object for this prop
  2384. //-----------------------------------------------------------------------------
  2385. void CPhysicsProp::Spawn( )
  2386. {
  2387. SetNetworkQuantizeOriginAngAngles( true );
  2388. if (HasSpawnFlags(SF_PHYSPROP_IS_GIB))
  2389. {
  2390. g_ActiveGibCount++;
  2391. }
  2392. // Condense classname's to one, except for "prop_physics_override"
  2393. if ( FClassnameIs( this, "physics_prop" ) )
  2394. {
  2395. SetClassname( "prop_physics" );
  2396. }
  2397. BaseClass::Spawn();
  2398. if ( IsMarkedForDeletion() )
  2399. return;
  2400. m_flFrozenThawRate = 0.1f;
  2401. // Now condense all classnames to one
  2402. if ( FClassnameIs( this, "prop_physics_override") )
  2403. {
  2404. SetClassname( "prop_physics" );
  2405. }
  2406. if ( HasSpawnFlags( SF_PHYSPROP_DEBRIS ) || HasInteraction( PROPINTER_PHYSGUN_CREATE_FLARE ) )
  2407. {
  2408. SetCollisionGroup( HasSpawnFlags( SF_PHYSPROP_FORCE_TOUCH_TRIGGERS ) ? COLLISION_GROUP_DEBRIS_TRIGGER : COLLISION_GROUP_DEBRIS );
  2409. }
  2410. if ( HasSpawnFlags( SF_PHYSPROP_NO_ROTORWASH_PUSH ) )
  2411. {
  2412. AddEFlags( EFL_NO_ROTORWASH_PUSH );
  2413. }
  2414. CreateVPhysics();
  2415. if ( !PropDataOverrodeBlockLOS() )
  2416. {
  2417. CalculateBlockLOS();
  2418. }
  2419. //Episode 1 change:
  2420. //Hi, since we're trying to ship this game we'll just go ahead and make all these doors not fade out instead of changing all the levels.
  2421. if ( Q_strcmp( STRING( GetModelName() ), "models/props_c17/door01_left.mdl" ) == 0 )
  2422. {
  2423. SetFadeDistance( -1, 0 );
  2424. DisableAutoFade();
  2425. }
  2426. // Set the AI AddOn from the QC key values
  2427. KeyValues *pModelKV = modelinfo->GetModelKeyValues( GetModel() );
  2428. if ( pModelKV )
  2429. {
  2430. KeyValues *pkvPropData = pModelKV->FindKey( "ai_addon" );
  2431. if ( pkvPropData )
  2432. {
  2433. SetAIAddOn( AllocPooledString( pkvPropData->GetString() ) );
  2434. return;
  2435. }
  2436. else
  2437. {
  2438. if ( GetAIAddOn().ToCStr()[ 0 ] == '\0' )
  2439. {
  2440. // No behavior, so set the default that this thing can be thrown
  2441. SetAIAddOn( MAKE_STRING( "ai_addon_thrownprojectile" ) );
  2442. }
  2443. }
  2444. }
  2445. // Do prop_physics_multiplayer stuff here
  2446. // if no physicsmode was defined by .QC or propdata.txt,
  2447. // use auto detect based on size & mass
  2448. if ( m_iPhysicsMode == PHYSICS_MULTIPLAYER_AUTODETECT )
  2449. {
  2450. if ( VPhysicsGetObject() )
  2451. {
  2452. m_iPhysicsMode = GetAutoMultiplayerPhysicsMode(
  2453. CollisionProp()->OBBSize(), VPhysicsGetObject()->GetMass() );
  2454. }
  2455. else
  2456. {
  2457. UTIL_Remove( this );
  2458. return;
  2459. }
  2460. }
  2461. // check if map maker overrides physics mode to force a server-side entity
  2462. if ( GetSpawnFlags() & SF_PHYSPROP_FORCE_SERVER_SIDE )
  2463. {
  2464. SetPhysicsMode( PHYSICS_MULTIPLAYER_NON_SOLID );
  2465. }
  2466. if ( m_iPhysicsMode == PHYSICS_MULTIPLAYER_CLIENTSIDE )
  2467. {
  2468. if ( engine->IsInEditMode() )
  2469. {
  2470. // in map edit mode always spawn as server phys prop
  2471. SetPhysicsMode( PHYSICS_MULTIPLAYER_NON_SOLID );
  2472. }
  2473. else if ( CommandLine()->FindParm( "-makereslists" ) )
  2474. {
  2475. // when building reslists always spawn as server phys prop
  2476. SetPhysicsMode( PHYSICS_MULTIPLAYER_NON_SOLID );
  2477. }
  2478. else
  2479. {
  2480. // don't spawn clientside props on server
  2481. UTIL_Remove( this );
  2482. return;
  2483. }
  2484. }
  2485. if ( IsPotentiallyAbleToObstructNavAreas() )
  2486. {
  2487. TheNavMesh->RegisterAvoidanceObstacle( this );
  2488. }
  2489. QAngle qPreffered;
  2490. if( GetPropDataAngles( "preferred_carryangles", qPreffered ) )
  2491. {
  2492. m_qPreferredPlayerCarryAngles = qPreffered;
  2493. }
  2494. }
  2495. //-----------------------------------------------------------------------------
  2496. // Purpose:
  2497. //-----------------------------------------------------------------------------
  2498. void CPhysicsProp::Precache( void )
  2499. {
  2500. if ( GetModelName() == NULL_STRING )
  2501. {
  2502. Msg( "%s at (%.3f, %.3f, %.3f) has no model name!\n", GetClassname(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
  2503. }
  2504. else
  2505. {
  2506. PrecacheModel( STRING( GetModelName() ) );
  2507. BaseClass::Precache();
  2508. }
  2509. }
  2510. //-----------------------------------------------------------------------------
  2511. // Purpose:
  2512. //-----------------------------------------------------------------------------
  2513. bool CPhysicsProp::CreateVPhysics()
  2514. {
  2515. // Create the object in the physics system
  2516. bool asleep = HasSpawnFlags( SF_PHYSPROP_START_ASLEEP ) ? true : false;
  2517. solid_t tmpSolid;
  2518. PhysModelParseSolid( tmpSolid, this, GetModelIndex() );
  2519. if ( m_massScale > 0 )
  2520. {
  2521. tmpSolid.params.mass *= m_massScale;
  2522. }
  2523. if ( m_inertiaScale > 0 )
  2524. {
  2525. tmpSolid.params.inertia *= m_inertiaScale;
  2526. if ( tmpSolid.params.inertia < 0.5 )
  2527. tmpSolid.params.inertia = 0.5;
  2528. }
  2529. PhysGetMassCenterOverride( this, modelinfo->GetVCollide( GetModelIndex() ), tmpSolid );
  2530. if ( HasSpawnFlags(SF_PHYSPROP_NO_COLLISIONS) )
  2531. {
  2532. tmpSolid.params.enableCollisions = false;
  2533. }
  2534. PhysSolidOverride( tmpSolid, m_iszOverrideScript );
  2535. IPhysicsObject *pPhysicsObject = VPhysicsInitNormal( SOLID_VPHYSICS, 0, asleep, &tmpSolid );
  2536. if ( !pPhysicsObject )
  2537. {
  2538. SetSolid( SOLID_NONE );
  2539. SetMoveType( MOVETYPE_NONE );
  2540. Warning("ERROR!: Can't create physics object for %s\n", STRING( GetModelName() ) );
  2541. return false;
  2542. }
  2543. if ( m_damageType == 1 )
  2544. {
  2545. PhysSetGameFlags( pPhysicsObject, FVPHYSICS_DMG_SLICE );
  2546. }
  2547. if ( HasSpawnFlags( SF_PHYSPROP_MOTIONDISABLED ) || m_damageToEnableMotion > 0 || m_flForceToEnableMotion > 0 )
  2548. {
  2549. pPhysicsObject->EnableMotion( false );
  2550. if ( m_damageToEnableMotion <= 0 && m_flForceToEnableMotion <= 0 )
  2551. {
  2552. AddSolidFlags(FSOLID_NOT_MOVEABLE);
  2553. }
  2554. }
  2555. // fix up any noncompliant blades.
  2556. if( pPhysicsObject && HasInteraction( PROPINTER_PHYSGUN_LAUNCH_SPIN_Z ) )
  2557. {
  2558. if( !(VPhysicsGetObject()->GetGameFlags() & FVPHYSICS_DMG_SLICE) )
  2559. {
  2560. PhysSetGameFlags( pPhysicsObject, FVPHYSICS_DMG_SLICE );
  2561. #if 0
  2562. if( g_pDeveloper->GetInt() )
  2563. {
  2564. // Highlight them in developer mode.
  2565. m_debugOverlays |= (OVERLAY_TEXT_BIT|OVERLAY_BBOX_BIT);
  2566. }
  2567. #endif
  2568. }
  2569. }
  2570. if( pPhysicsObject && HasInteraction( PROPINTER_PHYSGUN_DAMAGE_NONE ) )
  2571. {
  2572. PhysSetGameFlags( pPhysicsObject, FVPHYSICS_NO_IMPACT_DMG );
  2573. }
  2574. if ( pPhysicsObject && HasSpawnFlags(SF_PHYSPROP_PREVENT_PICKUP) )
  2575. {
  2576. PhysSetGameFlags(pPhysicsObject, FVPHYSICS_NO_PLAYER_PICKUP);
  2577. }
  2578. if ( !pPhysicsObject->IsMoveable() || pPhysicsObject->GetMass() >= VPHYSICS_LARGE_OBJECT_MASS )
  2579. {
  2580. m_bClientPhysics = true;
  2581. }
  2582. return true;
  2583. }
  2584. //-----------------------------------------------------------------------------
  2585. // Purpose:
  2586. // Output : Returns true on success, false on failure.
  2587. //-----------------------------------------------------------------------------
  2588. bool CPhysicsProp::CanBePickedUpByPhyscannon( void )
  2589. {
  2590. if ( HasSpawnFlags( SF_PHYSPROP_PREVENT_PICKUP ) )
  2591. return false;
  2592. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  2593. if ( pPhysicsObject && pPhysicsObject->IsMoveable() == false )
  2594. {
  2595. if ( HasSpawnFlags( SF_PHYSPROP_ENABLE_ON_PHYSCANNON ) == false )
  2596. return false;
  2597. }
  2598. return true;
  2599. }
  2600. //-----------------------------------------------------------------------------
  2601. // Purpose:
  2602. //-----------------------------------------------------------------------------
  2603. bool CPhysicsProp::OverridePropdata( void )
  2604. {
  2605. return ( FClassnameIs(this, "prop_physics_override" ) );
  2606. }
  2607. //-----------------------------------------------------------------------------
  2608. // Purpose: Input handler to start the physics prop simulating.
  2609. //-----------------------------------------------------------------------------
  2610. void CPhysicsProp::InputWake( inputdata_t &inputdata )
  2611. {
  2612. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  2613. if ( pPhysicsObject != NULL )
  2614. {
  2615. pPhysicsObject->Wake();
  2616. }
  2617. }
  2618. //-----------------------------------------------------------------------------
  2619. // Purpose: Input handler to stop the physics prop simulating.
  2620. //-----------------------------------------------------------------------------
  2621. void CPhysicsProp::InputSleep( inputdata_t &inputdata )
  2622. {
  2623. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  2624. if ( pPhysicsObject != NULL )
  2625. {
  2626. pPhysicsObject->Sleep();
  2627. }
  2628. }
  2629. //-----------------------------------------------------------------------------
  2630. // Purpose: Enable physics motion and collision response (on by default)
  2631. //-----------------------------------------------------------------------------
  2632. void CPhysicsProp::InputEnableMotion( inputdata_t &inputdata )
  2633. {
  2634. EnableMotion();
  2635. }
  2636. //-----------------------------------------------------------------------------
  2637. // Purpose: Disable any physics motion or collision response
  2638. //-----------------------------------------------------------------------------
  2639. void CPhysicsProp::InputDisableMotion( inputdata_t &inputdata )
  2640. {
  2641. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  2642. if ( pPhysicsObject != NULL )
  2643. {
  2644. pPhysicsObject->EnableMotion( false );
  2645. }
  2646. }
  2647. // Turn off floating simulation (and cost)
  2648. void CPhysicsProp::InputDisableFloating( inputdata_t &inputdata )
  2649. {
  2650. PhysEnableFloating( VPhysicsGetObject(), false );
  2651. }
  2652. //-----------------------------------------------------------------------------
  2653. // Purpose:
  2654. //-----------------------------------------------------------------------------
  2655. void CPhysicsProp::EnableMotion( void )
  2656. {
  2657. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  2658. if ( pPhysicsObject )
  2659. {
  2660. Vector pos;
  2661. QAngle angles;
  2662. if ( GetEnableMotionPosition( &pos, &angles ) )
  2663. {
  2664. ClearEnableMotionPosition();
  2665. //pPhysicsObject->SetPosition( pos, angles, true );
  2666. Teleport( &pos, &angles, NULL );
  2667. }
  2668. pPhysicsObject->EnableMotion( true );
  2669. pPhysicsObject->Wake();
  2670. m_MotionEnabled.FireOutput( this, this, 0 );
  2671. }
  2672. CheckRemoveRagdolls();
  2673. }
  2674. //-----------------------------------------------------------------------------
  2675. // Purpose:
  2676. //-----------------------------------------------------------------------------
  2677. void CPhysicsProp::OnPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason )
  2678. {
  2679. BaseClass::OnPhysGunPickup( pPhysGunUser, reason );
  2680. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  2681. if ( pPhysicsObject && !pPhysicsObject->IsMoveable() )
  2682. {
  2683. if ( !HasSpawnFlags( SF_PHYSPROP_ENABLE_ON_PHYSCANNON ) )
  2684. return;
  2685. EnableMotion();
  2686. if( HasInteraction( PROPINTER_PHYSGUN_WORLD_STICK ) )
  2687. {
  2688. SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
  2689. }
  2690. }
  2691. m_OnPhysGunPickup.FireOutput( pPhysGunUser, this );
  2692. if( reason == PICKED_UP_BY_CANNON )
  2693. {
  2694. m_OnPhysGunOnlyPickup.FireOutput( pPhysGunUser, this );
  2695. }
  2696. if ( reason == PUNTED_BY_CANNON )
  2697. {
  2698. m_OnPhysGunPunt.FireOutput( pPhysGunUser, this );
  2699. }
  2700. if ( reason == PICKED_UP_BY_CANNON || reason == PICKED_UP_BY_PLAYER )
  2701. {
  2702. m_OnPlayerPickup.FireOutput( pPhysGunUser, this );
  2703. }
  2704. CheckRemoveRagdolls();
  2705. }
  2706. //-----------------------------------------------------------------------------
  2707. // Purpose:
  2708. //-----------------------------------------------------------------------------
  2709. void CPhysicsProp::OnPhysGunDrop( CBasePlayer *pPhysGunUser, PhysGunDrop_t Reason )
  2710. {
  2711. BaseClass::OnPhysGunDrop( pPhysGunUser, Reason );
  2712. if ( Reason == LAUNCHED_BY_CANNON )
  2713. {
  2714. if ( HasInteraction( PROPINTER_PHYSGUN_LAUNCH_SPIN_Z ) )
  2715. {
  2716. AngularImpulse angVel( 0, 0, 5000.0 );
  2717. VPhysicsGetObject()->AddVelocity( NULL, &angVel );
  2718. // no angular drag on this object anymore
  2719. float angDrag = 0.0f;
  2720. VPhysicsGetObject()->SetDragCoefficient( NULL, &angDrag );
  2721. }
  2722. PhysSetGameFlags( VPhysicsGetObject(), FVPHYSICS_WAS_THROWN );
  2723. m_bFirstCollisionAfterLaunch = true;
  2724. }
  2725. else if ( Reason == THROWN_BY_PLAYER )
  2726. {
  2727. // Remember the player threw us for NPC response purposes
  2728. m_bThrownByPlayer = true;
  2729. }
  2730. m_OnPhysGunDrop.FireOutput( pPhysGunUser, this );
  2731. IGameEvent *event = gameeventmanager->CreateEvent( "player_drop" );
  2732. if ( event )
  2733. {
  2734. event->SetInt( "userid", pPhysGunUser ? pPhysGunUser->GetUserID() : 0 );
  2735. event->SetInt( "entity", entindex() );
  2736. gameeventmanager->FireEvent( event );
  2737. }
  2738. if ( HasInteraction( PROPINTER_PHYSGUN_NOTIFY_CHILDREN ) )
  2739. {
  2740. CUtlVector<CBaseEntity *> children;
  2741. GetAllChildren( this, children );
  2742. for (int i = 0; i < children.Count(); i++ )
  2743. {
  2744. CBaseEntity *pent = children.Element( i );
  2745. IParentPropInteraction *pPropInter = dynamic_cast<IParentPropInteraction *>( pent );
  2746. if ( pPropInter )
  2747. {
  2748. pPropInter->OnParentPhysGunDrop( pPhysGunUser, Reason );
  2749. }
  2750. }
  2751. }
  2752. }
  2753. //-----------------------------------------------------------------------------
  2754. // Purpose:
  2755. //-----------------------------------------------------------------------------
  2756. int CPhysicsProp::ObjectCaps()
  2757. {
  2758. int caps = BaseClass::ObjectCaps() | FCAP_WCEDIT_POSITION;
  2759. if ( HasSpawnFlags( SF_PHYSPROP_ENABLE_PICKUP_OUTPUT ) )
  2760. {
  2761. caps |= FCAP_IMPULSE_USE;
  2762. }
  2763. else if ( CBasePlayer::CanPickupObject( this, 35, 128 ) )
  2764. {
  2765. caps |= FCAP_IMPULSE_USE;
  2766. if( hl2_episodic.GetBool() && HasInteraction( PROPINTER_PHYSGUN_CREATE_FLARE ) )
  2767. {
  2768. caps |= FCAP_USE_IN_RADIUS;
  2769. }
  2770. }
  2771. if( HasSpawnFlags( SF_PHYSPROP_RADIUS_PICKUP ) )
  2772. {
  2773. caps |= FCAP_USE_IN_RADIUS;
  2774. }
  2775. return caps;
  2776. }
  2777. //-----------------------------------------------------------------------------
  2778. // Purpose:
  2779. // Input : *pActivator -
  2780. // *pCaller -
  2781. // useType -
  2782. // value -
  2783. //-----------------------------------------------------------------------------
  2784. void CPhysicsProp::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  2785. {
  2786. CBasePlayer *pPlayer = ToBasePlayer( pActivator );
  2787. if ( pPlayer )
  2788. {
  2789. if ( HasSpawnFlags( SF_PHYSPROP_ENABLE_PICKUP_OUTPUT ) )
  2790. {
  2791. m_OnPlayerUse.FireOutput( this, this );
  2792. }
  2793. pPlayer->PickupObject( this );
  2794. }
  2795. }
  2796. //-----------------------------------------------------------------------------
  2797. // Purpose: Return true if it's safe to disable movement on this physics prop, i.e. it's not in a situation where we shouldn't.
  2798. //-----------------------------------------------------------------------------
  2799. bool CPhysicsProp::ShouldDisableMotionOnFreeze( void )
  2800. {
  2801. // is it time to recheck all the contact points? (don't do this constantly as it's a waste of cpu)
  2802. if (gpGlobals->curtime < m_fNextCheckDisableMotionContactsTime )
  2803. return false;
  2804. m_fNextCheckDisableMotionContactsTime = gpGlobals->curtime + 0.5f;
  2805. IPhysicsObject *pPhysics = VPhysicsGetObject();
  2806. IPhysicsFrictionSnapshot *pSnapshot = pPhysics->CreateFrictionSnapshot();
  2807. CBaseEntity *pOtherEntity = NULL;
  2808. while ( pSnapshot->IsValid() )
  2809. {
  2810. IPhysicsObject *pOther = pSnapshot->GetObject(1);
  2811. pOtherEntity = static_cast<CBaseEntity*>(pOther->GetGameData());
  2812. CPhysicsProp *pPhysicsProp = dynamic_cast<CPhysicsProp*>(pOtherEntity);
  2813. if ( pPhysicsProp ) // we're touching another phys prop
  2814. {
  2815. // If this phys prop will never go motion disabled, we shouldn't either
  2816. if (!pPhysicsProp->HasSpawnFlags(SF_PHYSPROP_DISABLE_MOTION_ON_FREEZE))
  2817. return false;
  2818. }
  2819. pSnapshot->NextFrictionData();
  2820. }
  2821. pPhysics->DestroyFrictionSnapshot( pSnapshot );
  2822. return true;
  2823. }
  2824. //-----------------------------------------------------------------------------
  2825. // Purpose:
  2826. // Input : *pPhysics -
  2827. //-----------------------------------------------------------------------------
  2828. void CPhysicsProp::VPhysicsUpdate( IPhysicsObject *pPhysics )
  2829. {
  2830. BaseClass::VPhysicsUpdate( pPhysics );
  2831. m_bAwake = !pPhysics->IsAsleep();
  2832. IPhysicsObject *pPhysicsObject = VPhysicsGetObject();
  2833. NetworkStateChanged();
  2834. if ( HasSpawnFlags( SF_PHYSPROP_START_ASLEEP ) )
  2835. {
  2836. if ( m_bAwake )
  2837. {
  2838. m_OnAwakened.FireOutput(this, this);
  2839. RemoveSpawnFlags( SF_PHYSPROP_START_ASLEEP );
  2840. if (pPhysicsObject && pPhysicsObject->IsMoveable())
  2841. {
  2842. m_bHasBeenAwakened = true;
  2843. }
  2844. }
  2845. }
  2846. // If we're asleep, clear the player thrown flag
  2847. if ( m_bThrownByPlayer && !m_bAwake )
  2848. {
  2849. m_bThrownByPlayer = false;
  2850. }
  2851. if ( !IsInWorld() )
  2852. {
  2853. m_OnOutOfWorld.FireOutput( this, this );
  2854. }
  2855. // consider disabling motion
  2856. bool bAwake = ( m_bAwake && pPhysicsObject && pPhysicsObject->IsMoveable() );
  2857. if ( !bAwake && m_bHasBeenAwakened && HasSpawnFlags(SF_PHYSPROP_DISABLE_MOTION_ON_FREEZE) ) // prop has been woken at least once and is now asleep
  2858. {
  2859. // check we're not touching any props that don't have the disable motion flag
  2860. if ( ShouldDisableMotionOnFreeze() )
  2861. {
  2862. DevMsg("Disabling motion on phys prop");
  2863. pPhysicsObject->EnableMotion( false );
  2864. }
  2865. }
  2866. #ifdef PORTAL2
  2867. const float FUNNEL_MIN_VELOCITY_THRESHOLD = 64.0f;
  2868. const float FUNNEL_MIN_DIST_THRESHOLD = 128.0f;
  2869. static float g_flLastPropFunnelTime = 0.0f;
  2870. // Allow props to funnel toward a portal they're falling into
  2871. if ( sv_props_funnel_into_portals.GetBool() && m_bAllowPortalFunnel )
  2872. {
  2873. Vector vVelocity;
  2874. pPhysics->GetVelocity( &vVelocity, NULL );
  2875. // Make sure we're mostly going straight up or straight down
  2876. bool bFallingStraightDown = ( vVelocity.Length2DSqr() < Square(FUNNEL_MIN_VELOCITY_THRESHOLD) );
  2877. bool bFalling = vVelocity[2] < 0.0f;
  2878. if ( (fabs( vVelocity[2] ) >= 1.0f) && bFallingStraightDown )
  2879. {
  2880. float flSpeedSqr = vVelocity.Length2DSqr();
  2881. float flRampPerc = RemapValClamped( flSpeedSqr, Square(FUNNEL_MIN_VELOCITY_THRESHOLD*2), 0.0f, 0.0f, 1.0f );
  2882. Vector vPropOrigin;
  2883. pPhysics->GetPosition( &vPropOrigin, NULL );
  2884. int iPortalCount = CPortal_Base2D_Shared::AllPortals.Count();
  2885. if( iPortalCount != 0 )
  2886. {
  2887. CPortal_Base2D *pFunnelInto = NULL;
  2888. Vector vPropToFunnelPortal;
  2889. float fClosestFunnelPortalDistSqr = FLT_MAX;
  2890. CPortal_Base2D **pPortals = CPortal_Base2D_Shared::AllPortals.Base();
  2891. for( int i = 0; i != iPortalCount; ++i )
  2892. {
  2893. CPortal_Base2D *pTempPortal = pPortals[i];
  2894. if( pTempPortal->IsActivedAndLinked() )
  2895. {
  2896. // Make sure it's a floor or ceiling portal
  2897. if ( !pTempPortal->IsFloorPortal() )
  2898. continue;
  2899. Vector vPropToPortal = pTempPortal->m_ptOrigin - vPropOrigin;
  2900. // make sure that the portal isn't too far away and we aren't past it.
  2901. if ( ( vPropToPortal.z < -1024.0f) || (vPropToPortal.z >= 0.0f) )
  2902. continue;
  2903. Vector vPortalRight = pTempPortal->m_PortalSimulator.GetInternalData().Placement.vRight;
  2904. vPortalRight.z = 0.0f;
  2905. VectorNormalize( vPortalRight );
  2906. float fTestDist = pTempPortal->GetHalfWidth() * 1.5f;
  2907. fTestDist *= fTestDist;
  2908. // Make sure we're in the 2D portal rectangle
  2909. if ( ( vPropToPortal.Dot( vPortalRight ) * vPortalRight ).LengthSqr() > fTestDist )
  2910. continue;
  2911. Vector vPortalUp = pTempPortal->m_PortalSimulator.GetInternalData().Placement.vUp;
  2912. vPortalUp.z = 0.0f;
  2913. VectorNormalize( vPortalUp );
  2914. fTestDist = pTempPortal->GetHalfHeight() * 1.5f;
  2915. fTestDist *= fTestDist;
  2916. if ( ( vPropToPortal.Dot( vPortalUp ) * vPortalUp ).LengthSqr() > fTestDist )
  2917. continue;
  2918. float fDistSqr = vPropToPortal.LengthSqr();
  2919. if( fDistSqr < fClosestFunnelPortalDistSqr )
  2920. {
  2921. fClosestFunnelPortalDistSqr = fDistSqr;
  2922. pFunnelInto = pTempPortal;
  2923. vPropToFunnelPortal = vPropToPortal;
  2924. }
  2925. }
  2926. }
  2927. if ( pFunnelInto )
  2928. {
  2929. if( bFalling )
  2930. {
  2931. // Funnel toward the portal
  2932. float fFunnelX = vPropToFunnelPortal.x - vVelocity[ 0 ];
  2933. float fFunnelY = vPropToFunnelPortal.y - vVelocity[ 1 ];
  2934. // Ramp out as we get near the portal
  2935. float flDistRamp = RemapValClamped( vPropToFunnelPortal.z, -FUNNEL_MIN_DIST_THRESHOLD, -FUNNEL_MIN_DIST_THRESHOLD*2.0f, 0.0f, 1.0f );
  2936. vVelocity.x += fFunnelX * ( flRampPerc * flDistRamp );
  2937. vVelocity.y += fFunnelY * ( flRampPerc * flDistRamp );
  2938. // Take the new velocity
  2939. pPhysics->SetVelocity( &vVelocity, NULL );
  2940. }
  2941. else //shave off outward velocity while the object is going up
  2942. {
  2943. const float fMaxDeceleration = sv_props_funnel_into_portals_deceleration.GetFloat();
  2944. if( fMaxDeceleration > 0.0f )
  2945. {
  2946. const VPlane &portalPlane = pFunnelInto->m_PortalSimulator.GetInternalData().Placement.PortalPlane;
  2947. float fVelocityInPlaneDirection = portalPlane.m_Normal.Dot( vVelocity );
  2948. Vector vPlanarVelocity = (vVelocity - (portalPlane.m_Normal * fVelocityInPlaneDirection));
  2949. //to cancel all movement in the plane we would just subtract vPlanarVelocity. But we want to be pickier, and only cancel movement heading away from the portal
  2950. Vector vDistOnPlane = vPropToFunnelPortal - (vPropToFunnelPortal.Dot( portalPlane.m_Normal ) * portalPlane.m_Normal);
  2951. float fCancelDot = vPlanarVelocity.Dot( vDistOnPlane );
  2952. if( fCancelDot < 0.0f ) //less than zero because the distance vector is prop to portal instead of portal to prop
  2953. {
  2954. fCancelDot /= -(vDistOnPlane.Length());
  2955. if( fCancelDot > (fMaxDeceleration * TICK_INTERVAL) )
  2956. {
  2957. fCancelDot = (fMaxDeceleration * TICK_INTERVAL);
  2958. }
  2959. Vector vCancel = fCancelDot * vDistOnPlane; //project existing velocity onto position offset vector, which is the direction we want to cancel outward movement on
  2960. pPhysics->AddVelocity( &vCancel, NULL );
  2961. }
  2962. }
  2963. }
  2964. if ( g_flLastPropFunnelTime < gpGlobals->curtime )
  2965. {
  2966. // Msg( "Attempted to funnel physics prop towards approaching portal\n" );
  2967. g_flLastPropFunnelTime = gpGlobals->curtime + 2.0f;
  2968. }
  2969. }
  2970. }
  2971. }
  2972. else
  2973. {
  2974. // Reset the counter so we can warn again later
  2975. g_flLastPropFunnelTime = 0.0f;
  2976. }
  2977. }
  2978. #endif // PORTAL2
  2979. }
  2980. //-----------------------------------------------------------------------------
  2981. // Purpose:
  2982. //-----------------------------------------------------------------------------
  2983. bool CPhysicsProp::IsPotentiallyAbleToObstructNavAreas( void ) const
  2984. {
  2985. if ( !IsSolid() )
  2986. return false;
  2987. if ( IsSolidFlagSet( FSOLID_NOT_SOLID ) )
  2988. return false;
  2989. const float MinObstructingMass = 100.0f;
  2990. if ( GetMass() <= MinObstructingMass )
  2991. return false;
  2992. Extent extent;
  2993. CollisionProp()->WorldSpaceAABB( &extent.lo, &extent.hi );
  2994. return (extent.hi - extent.lo).IsLengthGreaterThan( StepHeight );
  2995. }
  2996. //-----------------------------------------------------------------------------
  2997. // Purpose:
  2998. //-----------------------------------------------------------------------------
  2999. float CPhysicsProp::GetNavObstructionHeight( void ) const
  3000. {
  3001. Extent extent;
  3002. CollisionProp()->WorldSpaceAABB( &extent.lo, &extent.hi );
  3003. return extent.hi.z - extent.lo.z;
  3004. }
  3005. //-----------------------------------------------------------------------------
  3006. // Purpose:
  3007. //-----------------------------------------------------------------------------
  3008. bool CPhysicsProp::CanObstructNavAreas( void ) const
  3009. {
  3010. if (m_bAwake )
  3011. return false;
  3012. const float MinObstructingMass = 100.0f;
  3013. if ( GetMass() <= MinObstructingMass )
  3014. return false;
  3015. if ( !IsSolid() )
  3016. return false;
  3017. if ( IsSolidFlagSet( FSOLID_NOT_SOLID ) )
  3018. return false;
  3019. Extent extent;
  3020. CollisionProp()->WorldSpaceAABB( &extent.lo, &extent.hi );
  3021. float height = extent.hi.z - extent.lo.z;
  3022. if ( height < StepHeight )
  3023. return false;
  3024. if ( GetHealth() < 300 && m_takedamage == DAMAGE_YES )
  3025. return false;
  3026. return true;
  3027. }
  3028. //-----------------------------------------------------------------------------
  3029. // Purpose:
  3030. //-----------------------------------------------------------------------------
  3031. void CPhysicsProp::OnNavMeshLoaded( void )
  3032. {
  3033. if ( !m_bAwake ) // tank walls have a different behavior
  3034. {
  3035. SetContextThink( &CPhysicsProp::NavThink, gpGlobals->curtime, "NavContext" );
  3036. }
  3037. }
  3038. //-----------------------------------------------------------------------------
  3039. // Purpose:
  3040. //-----------------------------------------------------------------------------
  3041. void CPhysicsProp::NavThink( void )
  3042. {
  3043. if ( !CanObstructNavAreas() )
  3044. return;
  3045. Extent extent;
  3046. CollisionProp()->WorldSpaceAABB( &extent.lo, &extent.hi );
  3047. extent.lo.z -= HumanHeight;
  3048. NavAreaCollector overlap;
  3049. TheNavMesh->ForAllAreasOverlappingExtent( overlap, extent );
  3050. float obstructionHeight = GetNavObstructionHeight();
  3051. FOR_EACH_VEC( overlap.m_area, it )
  3052. {
  3053. CNavArea *area = overlap.m_area[ it ];
  3054. area->MarkObstacleToAvoid( obstructionHeight );
  3055. }
  3056. }
  3057. //-----------------------------------------------------------------------------
  3058. // Purpose:
  3059. //-----------------------------------------------------------------------------
  3060. void CPhysicsProp::ClearFlagsThink( void )
  3061. {
  3062. // collision may have destroyed the physics object, recheck
  3063. if ( VPhysicsGetObject() )
  3064. {
  3065. PhysClearGameFlags( VPhysicsGetObject(), FVPHYSICS_WAS_THROWN );
  3066. SetContextThink( NULL, 0, "PROP_CLEARFLAGS" );
  3067. }
  3068. }
  3069. //-----------------------------------------------------------------------------
  3070. // Compute impulse to apply to the enabled entity.
  3071. //-----------------------------------------------------------------------------
  3072. void CPhysicsProp::ComputeEnablingImpulse( int index, gamevcollisionevent_t *pEvent )
  3073. {
  3074. // Surface speed of the object that hit us = v + w x r
  3075. // NOTE: w is specified in local space
  3076. Vector vecContactPoint, vecLocalContactPoint;
  3077. pEvent->pInternalData->GetContactPoint( vecContactPoint );
  3078. // Compute the angular component of velocity
  3079. IPhysicsObject *pImpactObject = pEvent->pObjects[!index];
  3080. pImpactObject->WorldToLocal( &vecLocalContactPoint, vecContactPoint );
  3081. vecLocalContactPoint -= pImpactObject->GetMassCenterLocalSpace();
  3082. Vector vecLocalContactVelocity, vecContactVelocity;
  3083. AngularImpulse vecAngularVelocity = pEvent->preAngularVelocity[!index];
  3084. vecAngularVelocity *= M_PI / 180.0f;
  3085. CrossProduct( vecAngularVelocity, vecLocalContactPoint, vecLocalContactVelocity );
  3086. pImpactObject->LocalToWorldVector( &vecContactVelocity, vecLocalContactVelocity );
  3087. // Add in the center-of-mass velocity
  3088. vecContactVelocity += pEvent->preVelocity[!index];
  3089. // Compute the force + torque to apply
  3090. vecContactVelocity *= pImpactObject->GetMass();
  3091. Vector vecForce;
  3092. AngularImpulse vecTorque;
  3093. pEvent->pObjects[index]->CalculateForceOffset( vecContactVelocity, vecContactPoint, &vecForce, &vecTorque );
  3094. PhysCallbackImpulse( pEvent->pObjects[index], vecForce, vecTorque );
  3095. }
  3096. //-----------------------------------------------------------------------------
  3097. // Purpose:
  3098. //-----------------------------------------------------------------------------
  3099. void CPhysicsProp::VPhysicsCollision( int index, gamevcollisionevent_t *pEvent )
  3100. {
  3101. BaseClass::VPhysicsCollision( index, pEvent );
  3102. IPhysicsObject *pPhysObj = pEvent->pObjects[!index];
  3103. if ( m_flForceToEnableMotion )
  3104. {
  3105. CBaseEntity *pOther = static_cast<CBaseEntity *>(pPhysObj->GetGameData());
  3106. // Don't allow the player to bump an object active if we've requested not to
  3107. if ( ( pOther && pOther->IsPlayer() && HasSpawnFlags( SF_PHYSPROP_PREVENT_PLAYER_TOUCH_ENABLE ) ) == false )
  3108. {
  3109. // Large enough to enable motion?
  3110. float flForce = pEvent->collisionSpeed * pPhysObj->GetMass();
  3111. if ( flForce >= m_flForceToEnableMotion )
  3112. {
  3113. ComputeEnablingImpulse( index, pEvent );
  3114. EnableMotion();
  3115. m_flForceToEnableMotion = 0;
  3116. }
  3117. }
  3118. }
  3119. if( m_bFirstCollisionAfterLaunch )
  3120. {
  3121. HandleFirstCollisionInteractions( index, pEvent );
  3122. }
  3123. if ( HasPhysicsAttacker( 2.0f ) )
  3124. {
  3125. HandleAnyCollisionInteractions( index, pEvent );
  3126. }
  3127. if ( !HasSpawnFlags( SF_PHYSPROP_DONT_TAKE_PHYSICS_DAMAGE ) )
  3128. {
  3129. int damageType = 0;
  3130. IBreakableWithPropData *pBreakableInterface = assert_cast<IBreakableWithPropData*>(this);
  3131. float damage = CalculateDefaultPhysicsDamage( index, pEvent, m_impactEnergyScale, true, damageType, pBreakableInterface->GetPhysicsDamageTable() );
  3132. if ( damage > 0 )
  3133. {
  3134. // Take extra damage after we're punted by the physcannon
  3135. if ( m_bFirstCollisionAfterLaunch && !m_bThrownByPlayer )
  3136. {
  3137. damage *= 10;
  3138. }
  3139. CBaseEntity *pHitEntity = pEvent->pEntities[!index];
  3140. if ( !pHitEntity )
  3141. {
  3142. // hit world
  3143. pHitEntity = GetContainingEntity( INDEXENT(0) );
  3144. }
  3145. Vector damagePos;
  3146. pEvent->pInternalData->GetContactPoint( damagePos );
  3147. Vector damageForce = pEvent->postVelocity[index] * pEvent->pObjects[index]->GetMass();
  3148. if ( damageForce == vec3_origin )
  3149. {
  3150. // This can happen if this entity is motion disabled, and can't move.
  3151. // Use the velocity of the entity that hit us instead.
  3152. damageForce = pEvent->postVelocity[!index] * pEvent->pObjects[!index]->GetMass();
  3153. }
  3154. // FIXME: this doesn't pass in who is responsible if some other entity "caused" this collision
  3155. PhysCallbackDamage( this, CTakeDamageInfo( pHitEntity, pHitEntity, damageForce, damagePos, damage, damageType ), *pEvent, index );
  3156. }
  3157. }
  3158. if ( m_bThrownByPlayer || m_bFirstCollisionAfterLaunch )
  3159. {
  3160. // If we were thrown by a player, and we've hit an NPC, let the NPC know
  3161. CBaseEntity *pHitEntity = pEvent->pEntities[!index];
  3162. if ( pHitEntity && pHitEntity->MyNPCPointer() )
  3163. {
  3164. pHitEntity->MyNPCPointer()->DispatchInteraction( g_interactionHitByPlayerThrownPhysObj, this, NULL );
  3165. m_bThrownByPlayer = false;
  3166. }
  3167. }
  3168. if ( m_bFirstCollisionAfterLaunch )
  3169. {
  3170. m_bFirstCollisionAfterLaunch = false;
  3171. // Setup the think function to remove the flags
  3172. RegisterThinkContext( "PROP_CLEARFLAGS" );
  3173. SetContextThink( &CPhysicsProp::ClearFlagsThink, gpGlobals->curtime, "PROP_CLEARFLAGS" );
  3174. }
  3175. }
  3176. //-----------------------------------------------------------------------------
  3177. // Purpose:
  3178. //-----------------------------------------------------------------------------
  3179. int CPhysicsProp::OnTakeDamage( const CTakeDamageInfo &info )
  3180. {
  3181. // note: if motion is disabled, OnTakeDamage can't apply physics force
  3182. int ret = BaseClass::OnTakeDamage( info );
  3183. if( IsOnFire() )
  3184. {
  3185. if( (info.GetDamageType() & DMG_BURN) && (info.GetDamageType() & DMG_DIRECT) )
  3186. {
  3187. // Burning! scare things in my path if I'm moving.
  3188. Vector vel;
  3189. if( VPhysicsGetObject() )
  3190. {
  3191. VPhysicsGetObject()->GetVelocity( &vel, NULL );
  3192. int dangerRadius = 256; // generous radius to begin with
  3193. if( hl2_episodic.GetBool() )
  3194. {
  3195. // In Episodic, burning items (such as destroyed APCs) are making very large
  3196. // danger sounds which frighten NPCs. This danger sound was designed to frighten
  3197. // NPCs away from burning objects that are about to explode (barrels, etc).
  3198. // So if this item has no more health (ie, has died but hasn't exploded),
  3199. // make a smaller danger sound, just to keep NPCs away from the flames.
  3200. // I suspect this problem didn't appear in HL2 simply because we didn't have
  3201. // NPCs in such close proximity to destroyed NPCs. (sjb)
  3202. if( GetHealth() < 1 )
  3203. {
  3204. // This item has no health, but still exists. That means that it may keep
  3205. // burning, but isn't likely to explode, so don't frighten over such a large radius.
  3206. dangerRadius = 120;
  3207. }
  3208. }
  3209. trace_t tr;
  3210. UTIL_TraceLine( WorldSpaceCenter(), WorldSpaceCenter() + vel, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr );
  3211. CSoundEnt::InsertSound( SOUND_DANGER, tr.endpos, dangerRadius, 1.0, this, SOUNDENT_CHANNEL_REPEATED_DANGER );
  3212. }
  3213. }
  3214. }
  3215. // If we have a force to enable motion, and we're still disabled, check to see if this should enable us
  3216. if ( m_flForceToEnableMotion )
  3217. {
  3218. // Large enough to enable motion?
  3219. float flForce = info.GetDamageForce().Length();
  3220. if ( flForce >= m_flForceToEnableMotion )
  3221. {
  3222. EnableMotion();
  3223. m_flForceToEnableMotion = 0;
  3224. }
  3225. }
  3226. // Check our health against the threshold:
  3227. if( m_damageToEnableMotion > 0 && GetHealth() < m_damageToEnableMotion )
  3228. {
  3229. // only do this once
  3230. m_damageToEnableMotion = 0;
  3231. // The damage that enables motion may have been enough damage to kill me if I'm breakable
  3232. // in which case my physics object is gone.
  3233. if ( VPhysicsGetObject() != NULL )
  3234. {
  3235. EnableMotion();
  3236. VPhysicsTakeDamage( info );
  3237. }
  3238. }
  3239. return ret;
  3240. }
  3241. //-----------------------------------------------------------------------------
  3242. // Mass / mass center
  3243. //-----------------------------------------------------------------------------
  3244. void CPhysicsProp::GetMassCenter( Vector *pMassCenter )
  3245. {
  3246. if ( !VPhysicsGetObject() )
  3247. {
  3248. pMassCenter->Init();
  3249. return;
  3250. }
  3251. Vector vecLocal = VPhysicsGetObject()->GetMassCenterLocalSpace();
  3252. VectorTransform( vecLocal, EntityToWorldTransform(), *pMassCenter );
  3253. }
  3254. float CPhysicsProp::GetMass() const
  3255. {
  3256. return VPhysicsGetObject() ? VPhysicsGetObject()->GetMass() : 1.0f;
  3257. }
  3258. //-----------------------------------------------------------------------------
  3259. // Purpose: Draw any debug text overlays
  3260. // Output : Current text offset from the top
  3261. //-----------------------------------------------------------------------------
  3262. int CPhysicsProp::DrawDebugTextOverlays(void)
  3263. {
  3264. int text_offset = BaseClass::DrawDebugTextOverlays();
  3265. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  3266. {
  3267. int r = 255;
  3268. int g = 255;
  3269. int b = 255;
  3270. if (VPhysicsGetObject())
  3271. {
  3272. char tempstr[512];
  3273. Q_snprintf(tempstr, sizeof(tempstr),"Mass: %.2f kg / %.2f lb (%s)", VPhysicsGetObject()->GetMass(), kg2lbs(VPhysicsGetObject()->GetMass()), GetMassEquivalent(VPhysicsGetObject()->GetMass()));
  3274. EntityText( text_offset, tempstr, 0,r,g,b);
  3275. text_offset++;
  3276. {
  3277. vphysics_objectstress_t stressOut;
  3278. float stress = CalculateObjectStress( VPhysicsGetObject(), this, &stressOut );
  3279. Q_snprintf(tempstr, sizeof(tempstr),"Stress: %.2f (%.2f / %.2f)", stress, stressOut.exertedStress, stressOut.receivedStress );
  3280. EntityText( text_offset, tempstr, 0,r,g,b);
  3281. text_offset++;
  3282. }
  3283. if ( !VPhysicsGetObject()->IsMoveable() )
  3284. {
  3285. Q_snprintf(tempstr, sizeof(tempstr),"Motion Disabled" );
  3286. EntityText( text_offset, tempstr, 0,r,g,b);
  3287. text_offset++;
  3288. }
  3289. if ( m_iszBasePropData != NULL_STRING )
  3290. {
  3291. Q_snprintf(tempstr, sizeof(tempstr),"Base PropData: %s", STRING(m_iszBasePropData) );
  3292. EntityText( text_offset, tempstr, 0,r,g,b);
  3293. text_offset++;
  3294. }
  3295. if ( m_iNumBreakableChunks != 0 )
  3296. {
  3297. IBreakableWithPropData *pBreakableInterface = assert_cast<IBreakableWithPropData*>(this);
  3298. Q_snprintf(tempstr, sizeof(tempstr),"Breakable Chunks: %d (Max Size %d)", (int) m_iNumBreakableChunks, pBreakableInterface->GetMaxBreakableSize() );
  3299. EntityText( text_offset, tempstr, 0,r,g,b);
  3300. text_offset++;
  3301. }
  3302. Q_snprintf(tempstr, sizeof(tempstr),"Skin: %d", m_nSkin.Get() );
  3303. EntityText( text_offset, tempstr, 0,r,g,b);
  3304. text_offset++;
  3305. Q_snprintf(tempstr, sizeof(tempstr),"Health: %d, collision group %d", GetHealth(), GetCollisionGroup() );
  3306. EntityText( text_offset, tempstr, 0,r,g,b);
  3307. text_offset++;
  3308. }
  3309. }
  3310. return text_offset;
  3311. }
  3312. static CBreakableProp *BreakModelCreate_Prop( CBaseEntity *pOwner, breakmodel_t *pModel, const Vector &position, const QAngle &angles, const breakablepropparams_t &params )
  3313. {
  3314. CBreakableProp *pEntity = (CBreakableProp *)CBaseEntity::CreateNoSpawn( "prop_physics", position, angles, pOwner );
  3315. if ( pEntity )
  3316. {
  3317. // UNDONE: Allow .qc to override spawnflags for child pieces
  3318. if ( pOwner )
  3319. {
  3320. pEntity->AddSpawnFlags( pOwner->GetSpawnFlags() );
  3321. // We never want to be motion disabled
  3322. pEntity->RemoveSpawnFlags( SF_PHYSPROP_MOTIONDISABLED );
  3323. }
  3324. pEntity->m_impactEnergyScale = params.impactEnergyScale; // assume the same material
  3325. // Inherit the base object's damage modifiers
  3326. CBreakableProp *pBreakableOwner = dynamic_cast<CBreakableProp *>(pOwner);
  3327. if ( pBreakableOwner )
  3328. {
  3329. pEntity->SetDmgModBullet( pBreakableOwner->GetDmgModBullet() );
  3330. pEntity->SetDmgModClub( pBreakableOwner->GetDmgModClub() );
  3331. pEntity->SetDmgModExplosive( pBreakableOwner->GetDmgModExplosive() );
  3332. pEntity->SetDmgModFire( pBreakableOwner->GetDmgModFire() );
  3333. // Copy over the dx7 fade too
  3334. pEntity->CopyFadeFrom( pBreakableOwner );
  3335. }
  3336. pEntity->SetModelName( AllocPooledString( pModel->modelName ) );
  3337. pEntity->SetModel( STRING(pEntity->GetModelName()) );
  3338. pEntity->SetCollisionGroup( pModel->collisionGroup );
  3339. if ( pModel->fadeMinDist > 0 && pModel->fadeMaxDist >= pModel->fadeMinDist )
  3340. {
  3341. pEntity->SetFadeDistance( pModel->fadeMinDist, pModel->fadeMaxDist );
  3342. }
  3343. if ( pModel->fadeTime != 0 )
  3344. {
  3345. pEntity->AddSpawnFlags( SF_PHYSPROP_IS_GIB );
  3346. }
  3347. pEntity->Spawn();
  3348. if ( prop_break_disable_float.GetBool() )
  3349. {
  3350. PhysEnableFloating( pEntity->VPhysicsGetObject(), false );
  3351. }
  3352. // If we're burning, break into burning pieces
  3353. CBaseAnimating *pAnimating = pOwner ? pOwner->GetBaseAnimating() : NULL;
  3354. if ( pAnimating && pAnimating->IsOnFire() )
  3355. {
  3356. CEntityFlame *pOwnerFlame = dynamic_cast<CEntityFlame*>( pAnimating->GetEffectEntity() );
  3357. if ( pOwnerFlame )
  3358. {
  3359. pEntity->Ignite( pOwnerFlame->GetRemainingLife(), false );
  3360. }
  3361. else
  3362. {
  3363. // This should never happen
  3364. pEntity->Ignite( random->RandomFloat( 5, 10 ), false );
  3365. }
  3366. }
  3367. }
  3368. return pEntity;
  3369. }
  3370. static CBaseAnimating *BreakModelCreate_Ragdoll( CBaseEntity *pOwner, breakmodel_t *pModel, const Vector &position, const QAngle &angles )
  3371. {
  3372. CBaseAnimating *pAnimating = CreateServerRagdollSubmodel( dynamic_cast<CBaseAnimating *>(pOwner), pModel->modelName, position, angles, pModel->collisionGroup );
  3373. return pAnimating;
  3374. }
  3375. CBaseEntity *BreakModelCreateSingle( CBaseEntity *pOwner, breakmodel_t *pModel, const Vector &position,
  3376. const QAngle &angles, const Vector &velocity, const AngularImpulse &angVelocity, int nSkin, const breakablepropparams_t &params )
  3377. {
  3378. CBaseAnimating *pEntity = NULL;
  3379. // stop creating gibs if too many
  3380. if ( g_ActiveGibCount >= ACTIVE_GIB_LIMIT )
  3381. {
  3382. //DevMsg(1,"Gib limit on %s\n", pModel->modelName );
  3383. return NULL;
  3384. }
  3385. if ( !pModel->isRagdoll )
  3386. {
  3387. pEntity = BreakModelCreate_Prop( pOwner, pModel, position, angles, params );
  3388. }
  3389. else
  3390. {
  3391. pEntity = BreakModelCreate_Ragdoll( pOwner, pModel, position, angles );
  3392. }
  3393. if ( pEntity )
  3394. {
  3395. pEntity->m_nSkin = nSkin;
  3396. pEntity->m_iHealth = pModel->health;
  3397. if ( g_ActiveGibCount >= ACTIVE_GIB_FADE )
  3398. {
  3399. pModel->fadeTime = MIN( 3, pModel->fadeTime );
  3400. }
  3401. if ( pModel->fadeTime )
  3402. {
  3403. pEntity->SUB_StartFadeOut( pModel->fadeTime, false );
  3404. CBreakableProp *pProp = dynamic_cast<CBreakableProp *>(pEntity);
  3405. if ( pProp && !pProp->GetNumBreakableChunks() && pProp->m_takedamage == DAMAGE_YES )
  3406. {
  3407. pProp->m_takedamage = DAMAGE_EVENTS_ONLY;
  3408. }
  3409. }
  3410. IPhysicsObject *pList[VPHYSICS_MAX_OBJECT_LIST_COUNT];
  3411. int count = pEntity->VPhysicsGetObjectList( pList, ARRAYSIZE(pList) );
  3412. if ( count )
  3413. {
  3414. for ( int i = 0; i < count; i++ )
  3415. {
  3416. pList[i]->SetVelocity( &velocity, &angVelocity );
  3417. }
  3418. }
  3419. else
  3420. {
  3421. // failed to create a physics object
  3422. UTIL_Remove( pEntity );
  3423. return NULL;
  3424. }
  3425. }
  3426. return pEntity;
  3427. }
  3428. class CBreakModelsPrecached : public CAutoGameSystem
  3429. {
  3430. public:
  3431. CBreakModelsPrecached() : CAutoGameSystem( "CBreakModelsPrecached" )
  3432. {
  3433. m_modelList.SetLessFunc( BreakLessFunc );
  3434. }
  3435. struct breakable_precache_t
  3436. {
  3437. string_t iszModelName;
  3438. int iBreakableCount;
  3439. };
  3440. static bool BreakLessFunc( breakable_precache_t const &lhs, breakable_precache_t const &rhs )
  3441. {
  3442. return ( lhs.iszModelName.ToCStr() < rhs.iszModelName.ToCStr() );
  3443. }
  3444. bool IsInList( string_t modelName, int *iBreakableCount )
  3445. {
  3446. breakable_precache_t sEntry;
  3447. sEntry.iszModelName = modelName;
  3448. int iEntry = m_modelList.Find(sEntry);
  3449. if ( iEntry != m_modelList.InvalidIndex() )
  3450. {
  3451. *iBreakableCount = m_modelList[iEntry].iBreakableCount;
  3452. return true;
  3453. }
  3454. return false;
  3455. }
  3456. void AddToList( string_t modelName, int iBreakableCount )
  3457. {
  3458. breakable_precache_t sEntry;
  3459. sEntry.iszModelName = modelName;
  3460. sEntry.iBreakableCount = iBreakableCount;
  3461. m_modelList.Insert( sEntry );
  3462. }
  3463. void LevelShutdownPostEntity()
  3464. {
  3465. m_modelList.RemoveAll();
  3466. }
  3467. private:
  3468. CUtlRBTree<breakable_precache_t> m_modelList;
  3469. };
  3470. static CBreakModelsPrecached g_BreakModelsPrecached;
  3471. int PropBreakablePrecacheAll( string_t modelName )
  3472. {
  3473. int iBreakables = 0;
  3474. if ( g_BreakModelsPrecached.IsInList( modelName, &iBreakables ) )
  3475. return iBreakables;
  3476. if ( modelName == NULL_STRING )
  3477. {
  3478. Msg("Trying to precache breakable prop, but has no model name\n");
  3479. return iBreakables;
  3480. }
  3481. int modelIndex = CBaseEntity::PrecacheModel( STRING(modelName) );
  3482. CUtlVector<breakmodel_t> list;
  3483. BreakModelList( list, modelIndex, COLLISION_GROUP_NONE, 0 );
  3484. iBreakables = list.Count();
  3485. g_BreakModelsPrecached.AddToList( modelName, iBreakables );
  3486. for ( int i = 0; i < iBreakables; i++ )
  3487. {
  3488. string_t breakModelName = AllocPooledString(list[i].modelName);
  3489. if ( modelIndex <= 0 )
  3490. {
  3491. iBreakables--;
  3492. continue;
  3493. }
  3494. PropBreakablePrecacheAll( breakModelName );
  3495. }
  3496. return iBreakables;
  3497. }
  3498. bool PropBreakableCapEdictsOnCreateAll( CUtlVector<breakmodel_t> &list, IPhysicsObject *pPhysics, const breakablepropparams_t &params, CBaseEntity *pEntity, int iPrecomputedBreakableCount = -1 )
  3499. {
  3500. // @Note (toml 10-07-03): this is stop-gap to prevent this function from crashing the engine
  3501. const int BREATHING_ROOM = 64;
  3502. int nCurrentEntityCount = engine->GetEntityCount();
  3503. int numToCreate = 0;
  3504. if ( iPrecomputedBreakableCount != -1 )
  3505. {
  3506. numToCreate = iPrecomputedBreakableCount;
  3507. }
  3508. else
  3509. {
  3510. if ( list.Count() )
  3511. {
  3512. // if there are enough don't bother checking each piece
  3513. int nCurrentAvailable = MAX_EDICTS - (nCurrentEntityCount + BREATHING_ROOM);
  3514. if ( nCurrentAvailable > list.Count() )
  3515. {
  3516. numToCreate = list.Count();
  3517. }
  3518. else
  3519. {
  3520. for ( int i = 0; i < list.Count(); i++ )
  3521. {
  3522. int modelIndex = modelinfo->GetModelIndex( list[i].modelName );
  3523. if ( modelIndex <= 0 )
  3524. continue;
  3525. numToCreate++;
  3526. }
  3527. }
  3528. }
  3529. // Then see if the propdata specifies any breakable pieces
  3530. else if ( pEntity )
  3531. {
  3532. IBreakableWithPropData *pBreakableInterface = dynamic_cast<IBreakableWithPropData*>(pEntity);
  3533. if ( pBreakableInterface && pBreakableInterface->GetBreakableModel() != NULL_STRING && pBreakableInterface->GetBreakableCount() )
  3534. {
  3535. numToCreate += pBreakableInterface->GetBreakableCount();
  3536. }
  3537. }
  3538. }
  3539. return ( !numToCreate || ( nCurrentEntityCount + numToCreate + BREATHING_ROOM < MAX_EDICTS ) );
  3540. }
  3541. //=============================================================================================================
  3542. // BASE PROP DOOR
  3543. //=============================================================================================================
  3544. //
  3545. // Private activities.
  3546. //
  3547. static int ACT_DOOR_OPEN = 0;
  3548. static int ACT_DOOR_LOCKED = 0;
  3549. //
  3550. // Anim events.
  3551. //
  3552. enum
  3553. {
  3554. AE_DOOR_OPEN = 1, // The door should start opening.
  3555. };
  3556. void PlayLockSounds(CBaseEntity *pEdict, locksound_t *pls, int flocked, int fbutton);
  3557. BEGIN_DATADESC_NO_BASE(locksound_t)
  3558. DEFINE_FIELD( sLockedSound, FIELD_STRING),
  3559. DEFINE_FIELD( sLockedSentence, FIELD_STRING ),
  3560. DEFINE_FIELD( sUnlockedSound, FIELD_STRING ),
  3561. DEFINE_FIELD( sUnlockedSentence, FIELD_STRING ),
  3562. DEFINE_FIELD( iLockedSentence, FIELD_INTEGER ),
  3563. DEFINE_FIELD( iUnlockedSentence, FIELD_INTEGER ),
  3564. DEFINE_FIELD( flwaitSound, FIELD_FLOAT ),
  3565. DEFINE_FIELD( flwaitSentence, FIELD_FLOAT ),
  3566. DEFINE_FIELD( bEOFLocked, FIELD_CHARACTER ),
  3567. DEFINE_FIELD( bEOFUnlocked, FIELD_CHARACTER ),
  3568. END_DATADESC()
  3569. BEGIN_DATADESC(CBasePropDoor)
  3570. //DEFINE_FIELD(m_bLockedSentence, FIELD_CHARACTER),
  3571. //DEFINE_FIELD(m_bUnlockedSentence, FIELD_CHARACTER),
  3572. DEFINE_KEYFIELD(m_nHardwareType, FIELD_INTEGER, "hardware"),
  3573. DEFINE_KEYFIELD(m_flAutoReturnDelay, FIELD_FLOAT, "returndelay"),
  3574. DEFINE_FIELD( m_hActivator, FIELD_EHANDLE ),
  3575. DEFINE_KEYFIELD(m_SoundMoving, FIELD_SOUNDNAME, "soundmoveoverride"),
  3576. DEFINE_KEYFIELD(m_SoundOpen, FIELD_SOUNDNAME, "soundopenoverride"),
  3577. DEFINE_KEYFIELD(m_SoundClose, FIELD_SOUNDNAME, "soundcloseoverride"),
  3578. DEFINE_KEYFIELD(m_ls.sLockedSound, FIELD_SOUNDNAME, "soundlockedoverride"),
  3579. DEFINE_KEYFIELD(m_ls.sUnlockedSound, FIELD_SOUNDNAME, "soundunlockedoverride"),
  3580. DEFINE_KEYFIELD(m_SlaveName, FIELD_STRING, "slavename" ),
  3581. DEFINE_FIELD(m_bLocked, FIELD_BOOLEAN),
  3582. //DEFINE_KEYFIELD(m_flBlockDamage, FIELD_FLOAT, "dmg"),
  3583. DEFINE_KEYFIELD( m_bForceClosed, FIELD_BOOLEAN, "forceclosed" ),
  3584. DEFINE_FIELD(m_eDoorState, FIELD_INTEGER),
  3585. DEFINE_FIELD( m_hMaster, FIELD_EHANDLE ),
  3586. DEFINE_FIELD( m_hBlocker, FIELD_EHANDLE ),
  3587. DEFINE_FIELD( m_bFirstBlocked, FIELD_BOOLEAN ),
  3588. //DEFINE_FIELD(m_hDoorList, FIELD_CLASSPTR), // Reconstructed
  3589. DEFINE_INPUTFUNC(FIELD_VOID, "Open", InputOpen),
  3590. DEFINE_INPUTFUNC(FIELD_STRING, "OpenAwayFrom", InputOpenAwayFrom),
  3591. DEFINE_INPUTFUNC(FIELD_VOID, "Close", InputClose),
  3592. DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle),
  3593. DEFINE_INPUTFUNC(FIELD_VOID, "Lock", InputLock),
  3594. DEFINE_INPUTFUNC(FIELD_VOID, "Unlock", InputUnlock),
  3595. DEFINE_OUTPUT(m_OnBlockedOpening, "OnBlockedOpening"),
  3596. DEFINE_OUTPUT(m_OnBlockedClosing, "OnBlockedClosing"),
  3597. DEFINE_OUTPUT(m_OnUnblockedOpening, "OnUnblockedOpening"),
  3598. DEFINE_OUTPUT(m_OnUnblockedClosing, "OnUnblockedClosing"),
  3599. DEFINE_OUTPUT(m_OnFullyClosed, "OnFullyClosed"),
  3600. DEFINE_OUTPUT(m_OnFullyOpen, "OnFullyOpen"),
  3601. DEFINE_OUTPUT(m_OnClose, "OnClose"),
  3602. DEFINE_OUTPUT(m_OnOpen, "OnOpen"),
  3603. DEFINE_OUTPUT(m_OnLockedUse, "OnLockedUse" ),
  3604. DEFINE_EMBEDDED( m_ls ),
  3605. // Function Pointers
  3606. DEFINE_THINKFUNC(DoorOpenMoveDone),
  3607. DEFINE_THINKFUNC(DoorCloseMoveDone),
  3608. DEFINE_THINKFUNC(DoorAutoCloseThink),
  3609. DEFINE_THINKFUNC(DisableAreaPortalThink),
  3610. END_DATADESC()
  3611. IMPLEMENT_SERVERCLASS_ST(CBasePropDoor, DT_BasePropDoor)
  3612. //--------------------------------------------------------------------------------------------------------
  3613. // Datatable reduction
  3614. SendPropExclude( "DT_BaseAnimating", "m_flPoseParameter" ),
  3615. SendPropExclude( "DT_BaseAnimating", "m_flPlaybackRate" ),
  3616. //SendPropExclude( "DT_BaseAnimating", "m_nSequence" ),
  3617. //SendPropExclude( "DT_BaseAnimating", "m_nNewSequenceParity" ),
  3618. //SendPropExclude( "DT_BaseAnimating", "m_nResetEventsParity" ),
  3619. SendPropExclude( "DT_BaseAnimating", "m_nMuzzleFlashParity" ),
  3620. //SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
  3621. SendPropExclude( "DT_BaseAnimatingOverlay", "overlay_vars" ),
  3622. SendPropExclude( "DT_BaseFlex", "m_flexWeight" ),
  3623. SendPropExclude( "DT_BaseFlex", "m_blinktoggle" ),
  3624. // calc mins/maxs on the client, since we have all the info
  3625. //SendPropExclude( "DT_CollisionProperty", "m_vecMins" ),
  3626. //SendPropExclude( "DT_CollisionProperty", "m_vecMaxs" ),
  3627. //SendPropExclude( "DT_ServerAnimationData" , "m_flCycle" ),
  3628. #ifdef TERROR
  3629. SendPropExclude( "DT_AnimTimeMustBeFirst" , "m_flAnimTime" ),
  3630. #endif
  3631. //--------------------------------------------------------------------------------------------------------
  3632. // SendPropInt( SENDINFO(m_spawnflags), 16, SPROP_UNSIGNED ),
  3633. END_SEND_TABLE()
  3634. CBasePropDoor::CBasePropDoor( void )
  3635. {
  3636. m_hMaster = NULL;
  3637. m_nPhysicsMaterial = -1;
  3638. }
  3639. //-----------------------------------------------------------------------------
  3640. // Purpose:
  3641. //-----------------------------------------------------------------------------
  3642. void CBasePropDoor::Spawn()
  3643. {
  3644. BaseClass::Spawn();
  3645. DisableAutoFade();
  3646. Precache();
  3647. DoorTeleportToSpawnPosition();
  3648. if (HasSpawnFlags(SF_DOOR_LOCKED))
  3649. {
  3650. m_bLocked = true;
  3651. }
  3652. SetMoveType(MOVETYPE_PUSH);
  3653. if (m_flSpeed == 0)
  3654. {
  3655. m_flSpeed = 100;
  3656. }
  3657. RemoveFlag(FL_STATICPROP);
  3658. SetSolid(SOLID_VPHYSICS);
  3659. VPhysicsInitShadow(false, false);
  3660. AddSolidFlags( FSOLID_CUSTOMRAYTEST | FSOLID_CUSTOMBOXTEST );
  3661. SetBodygroup( DOOR_HARDWARE_GROUP, m_nHardwareType );
  3662. if ((m_nHardwareType == 0) && (!HasSpawnFlags(SF_DOOR_LOCKED)))
  3663. {
  3664. // Doors with no hardware must always be locked.
  3665. DevWarning(1, "Unlocked prop_door '%s' at (%.0f %.0f %.0f) has no hardware. All openable doors must have hardware!\n", GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z);
  3666. }
  3667. if ( !PropDataOverrodeBlockLOS() )
  3668. {
  3669. CalculateBlockLOS();
  3670. }
  3671. SetDoorBlocker( NULL );
  3672. // Fills out the m_Soundxxx members.
  3673. CalcDoorSounds();
  3674. }
  3675. //-----------------------------------------------------------------------------
  3676. bool CBasePropDoor::IsAbleToCloseAreaPortals( void ) const
  3677. {
  3678. return true;
  3679. }
  3680. // Purpose: Returns our capabilities mask.
  3681. //-----------------------------------------------------------------------------
  3682. int CBasePropDoor::ObjectCaps()
  3683. {
  3684. return BaseClass::ObjectCaps() | ( HasSpawnFlags( SF_DOOR_IGNORE_USE ) ? 0 : (FCAP_IMPULSE_USE|FCAP_USE_IN_RADIUS) );
  3685. };
  3686. //-----------------------------------------------------------------------------
  3687. // Purpose:
  3688. //-----------------------------------------------------------------------------
  3689. void CBasePropDoor::Precache(void)
  3690. {
  3691. BaseClass::Precache();
  3692. RegisterPrivateActivities();
  3693. }
  3694. //-----------------------------------------------------------------------------
  3695. // Purpose:
  3696. //-----------------------------------------------------------------------------
  3697. void CBasePropDoor::RegisterPrivateActivities(void)
  3698. {
  3699. static bool bRegistered = false;
  3700. if (bRegistered)
  3701. return;
  3702. REGISTER_PRIVATE_ACTIVITY( ACT_DOOR_OPEN );
  3703. REGISTER_PRIVATE_ACTIVITY( ACT_DOOR_LOCKED );
  3704. }
  3705. //-----------------------------------------------------------------------------
  3706. // Purpose:
  3707. //-----------------------------------------------------------------------------
  3708. void CBasePropDoor::Activate( void )
  3709. {
  3710. BaseClass::Activate();
  3711. UpdateAreaPortals( !IsDoorClosed() );
  3712. // If we have a name, we may be linked
  3713. if ( GetEntityName() != NULL_STRING )
  3714. {
  3715. CBaseEntity *pTarget = NULL;
  3716. // Find our slaves.
  3717. // If we have a specified slave name, then use that to find slaves.
  3718. // Otherwise, see if there are any other doors that match our name (Backwards compatability).
  3719. string_t iszSearchName = GetEntityName();
  3720. if ( m_SlaveName != NULL_STRING )
  3721. {
  3722. const char *pSlaveName = STRING(m_SlaveName);
  3723. if ( pSlaveName && pSlaveName[0] )
  3724. {
  3725. iszSearchName = m_SlaveName;
  3726. }
  3727. }
  3728. while ( ( pTarget = gEntList.FindEntityByName( pTarget, iszSearchName ) ) != NULL )
  3729. {
  3730. if ( pTarget != this )
  3731. {
  3732. CBasePropDoor *pDoor = dynamic_cast<CBasePropDoor *>(pTarget);
  3733. if ( pDoor != NULL && pDoor->HasSlaves() == false )
  3734. {
  3735. m_hDoorList.AddToTail( pDoor );
  3736. pDoor->SetMaster( this );
  3737. pDoor->SetOwnerEntity( this );
  3738. }
  3739. }
  3740. }
  3741. }
  3742. }
  3743. //-----------------------------------------------------------------------------
  3744. // Purpose:
  3745. //-----------------------------------------------------------------------------
  3746. void CBasePropDoor::HandleAnimEvent(animevent_t *pEvent)
  3747. {
  3748. // Opening is called here via an animation event if the open sequence has one,
  3749. // otherwise it is called immediately when the open sequence is set.
  3750. if ( pEvent->Event() == AE_DOOR_OPEN )
  3751. {
  3752. DoorActivate();
  3753. return;
  3754. }
  3755. BaseClass::HandleAnimEvent( pEvent );
  3756. }
  3757. // Only overwrite str1 if it's NULL_STRING.
  3758. #define ASSIGN_STRING_IF_NULL( str1, str2 ) \
  3759. if ( ( str1 ) == NULL_STRING ) { ( str1 ) = ( str2 ); }
  3760. //-----------------------------------------------------------------------------
  3761. // Purpose:
  3762. //-----------------------------------------------------------------------------
  3763. void CBasePropDoor::CalcDoorSounds()
  3764. {
  3765. ErrorIfNot( GetModel() != NULL, ( "prop_door with no model at %.2f %.2f %.2f\n", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z ) );
  3766. string_t strSoundOpen = NULL_STRING;
  3767. string_t strSoundClose = NULL_STRING;
  3768. string_t strSoundMoving = NULL_STRING;
  3769. string_t strSoundLocked = NULL_STRING;
  3770. string_t strSoundUnlocked = NULL_STRING;
  3771. // Otherwise, use the sounds specified by the model keyvalues. These are looked up
  3772. // based on skin and hardware.
  3773. KeyValues *pModelKV = modelinfo->GetModelKeyValues( GetModel() );
  3774. if ( pModelKV )
  3775. {
  3776. KeyValues *pkvDoorSounds = pModelKV->FindKey("door_options");
  3777. if ( pkvDoorSounds )
  3778. {
  3779. // Open / close / move sounds are looked up by skin index.
  3780. char szSkin[80];
  3781. int skin = m_nSkin;
  3782. Q_snprintf( szSkin, sizeof( szSkin ), "skin%d", skin );
  3783. KeyValues *pkvSkinData = pkvDoorSounds->FindKey( szSkin );
  3784. if ( pkvSkinData )
  3785. {
  3786. strSoundOpen = AllocPooledString( pkvSkinData->GetString( "open" ) );
  3787. strSoundClose = AllocPooledString( pkvSkinData->GetString( "close" ) );
  3788. strSoundMoving = AllocPooledString( pkvSkinData->GetString( "move" ) );
  3789. if ( m_nPhysicsMaterial == -1 )
  3790. {
  3791. const char *pSurfaceprop = pkvSkinData->GetString( "surfaceprop" );
  3792. if ( pSurfaceprop && VPhysicsGetObject() )
  3793. {
  3794. m_nPhysicsMaterial = physprops->GetSurfaceIndex( pSurfaceprop );
  3795. }
  3796. }
  3797. }
  3798. // Locked / unlocked sounds are looked up by hardware index.
  3799. char szHardware[80];
  3800. Q_snprintf( szHardware, sizeof( szHardware ), "hardware%d", m_nHardwareType );
  3801. KeyValues *pkvHardwareData = pkvDoorSounds->FindKey( szHardware );
  3802. if ( pkvHardwareData )
  3803. {
  3804. strSoundLocked = AllocPooledString( pkvHardwareData->GetString( "locked" ) );
  3805. strSoundUnlocked = AllocPooledString( pkvHardwareData->GetString( "unlocked" ) );
  3806. }
  3807. // If any sounds were missing, try the "defaults" block.
  3808. if ( ( strSoundOpen == NULL_STRING ) || ( strSoundClose == NULL_STRING ) || ( strSoundMoving == NULL_STRING ) ||
  3809. ( strSoundLocked == NULL_STRING ) || ( strSoundUnlocked == NULL_STRING ) )
  3810. {
  3811. KeyValues *pkvDefaults = pkvDoorSounds->FindKey( "defaults" );
  3812. if ( pkvDefaults )
  3813. {
  3814. ASSIGN_STRING_IF_NULL( strSoundOpen, AllocPooledString( pkvDefaults->GetString( "open" ) ) );
  3815. ASSIGN_STRING_IF_NULL( strSoundClose, AllocPooledString( pkvDefaults->GetString( "close" ) ) );
  3816. ASSIGN_STRING_IF_NULL( strSoundMoving, AllocPooledString( pkvDefaults->GetString( "move" ) ) );
  3817. ASSIGN_STRING_IF_NULL( strSoundLocked, AllocPooledString( pkvDefaults->GetString( "locked" ) ) );
  3818. ASSIGN_STRING_IF_NULL( strSoundUnlocked, AllocPooledString( pkvDefaults->GetString( "unlocked" ) ) );
  3819. // The model should already have a surfaceprop, which is authoritative. But in the event it doesn't, we may as well populate it here
  3820. // instead of give up and assign the hardcoded "wood" property later.
  3821. if ( m_nPhysicsMaterial == -1 )
  3822. {
  3823. const char *pSurfaceprop = pkvDefaults->GetString( "surfaceprop" );
  3824. if ( pSurfaceprop && VPhysicsGetObject() )
  3825. {
  3826. m_nPhysicsMaterial = physprops->GetSurfaceIndex( pSurfaceprop );
  3827. }
  3828. }
  3829. }
  3830. }
  3831. }
  3832. }
  3833. if ( VPhysicsGetObject() )
  3834. {
  3835. if ( m_nPhysicsMaterial == -1 )
  3836. {
  3837. Warning( "%s has Door model (%s) with no door_options or m_nPhysicsMaterial specified! Verify that SKIN is valid, and has a corresponding options block in the model QC file\n", GetDebugName(), modelinfo->GetModelName( GetModel() ) );
  3838. VPhysicsGetObject()->SetMaterialIndex( physprops->GetSurfaceIndex("wood") );
  3839. }
  3840. else
  3841. {
  3842. VPhysicsGetObject()->SetMaterialIndex( m_nPhysicsMaterial );
  3843. }
  3844. }
  3845. // Any sound data members that are already filled out were specified as level designer overrides,
  3846. // so they should not be overwritten.
  3847. ASSIGN_STRING_IF_NULL( m_SoundOpen, strSoundOpen );
  3848. ASSIGN_STRING_IF_NULL( m_SoundClose, strSoundClose );
  3849. ASSIGN_STRING_IF_NULL( m_SoundMoving, strSoundMoving );
  3850. ASSIGN_STRING_IF_NULL( m_ls.sLockedSound, strSoundLocked );
  3851. ASSIGN_STRING_IF_NULL( m_ls.sUnlockedSound, strSoundUnlocked );
  3852. // Make sure we have real, precachable sound names in all cases.
  3853. UTIL_ValidateSoundName( m_SoundMoving, "DoorSound.Null" );
  3854. UTIL_ValidateSoundName( m_SoundOpen, "DoorSound.Null" );
  3855. UTIL_ValidateSoundName( m_SoundClose, "DoorSound.Null" );
  3856. UTIL_ValidateSoundName( m_ls.sLockedSound, "DoorSound.Null" );
  3857. UTIL_ValidateSoundName( m_ls.sUnlockedSound, "DoorSound.Null" );
  3858. PrecacheScriptSound( STRING( m_SoundMoving ) );
  3859. PrecacheScriptSound( STRING( m_SoundOpen ) );
  3860. PrecacheScriptSound( STRING( m_SoundClose ) );
  3861. PrecacheScriptSound( STRING( m_ls.sLockedSound ) );
  3862. PrecacheScriptSound( STRING( m_ls.sUnlockedSound ) );
  3863. }
  3864. //-----------------------------------------------------------------------------
  3865. // Purpose: Delay closing of area portals
  3866. //-----------------------------------------------------------------------------
  3867. void CBasePropDoor::DisableAreaPortalThink( void )
  3868. {
  3869. UpdateAreaPortals( false );
  3870. }
  3871. //-----------------------------------------------------------------------------
  3872. // Purpose:
  3873. // Input : isOpen -
  3874. //-----------------------------------------------------------------------------
  3875. void CBasePropDoor::UpdateAreaPortals(bool isOpen)
  3876. {
  3877. SetContextThink( NULL, 0, "AreaPortal" );
  3878. if ( !IsAbleToCloseAreaPortals() )
  3879. {
  3880. isOpen = true;
  3881. }
  3882. string_t name = GetEntityName();
  3883. if (!name)
  3884. return;
  3885. CBaseEntity *pPortal = NULL;
  3886. while ((pPortal = gEntList.FindEntityByClassname(pPortal, "func_areaportal")) != NULL)
  3887. {
  3888. if (pPortal->HasTarget(name))
  3889. {
  3890. // USE_ON means open the portal, off means close it
  3891. pPortal->Use(this, this, isOpen?USE_ON:USE_OFF, 0);
  3892. }
  3893. }
  3894. }
  3895. //-----------------------------------------------------------------------------
  3896. // Purpose:
  3897. // Input : state -
  3898. //-----------------------------------------------------------------------------
  3899. void CBasePropDoor::SetDoorBlocker( CBaseEntity *pBlocker )
  3900. {
  3901. m_hBlocker = pBlocker;
  3902. if ( m_hBlocker == NULL )
  3903. {
  3904. m_bFirstBlocked = false;
  3905. }
  3906. }
  3907. //-----------------------------------------------------------------------------
  3908. // Purpose: Called when the player uses the door.
  3909. // Input : pActivator -
  3910. // pCaller -
  3911. // useType -
  3912. // value -
  3913. //-----------------------------------------------------------------------------
  3914. void CBasePropDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value)
  3915. {
  3916. if ( GetMaster() != NULL )
  3917. {
  3918. // Tell our owner we've been used
  3919. GetMaster()->Use( pActivator, pCaller, useType, value );
  3920. }
  3921. else
  3922. {
  3923. // Just let it through
  3924. OnUse( pActivator, pCaller, useType, value );
  3925. }
  3926. }
  3927. //-----------------------------------------------------------------------------
  3928. // Purpose:
  3929. // Input : *pActivator -
  3930. // *pCaller -
  3931. // useType -
  3932. // value -
  3933. //-----------------------------------------------------------------------------
  3934. void CBasePropDoor::OnUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
  3935. {
  3936. // If we're blocked while closing, open away from our blocker. This will
  3937. // liberate whatever bit of detritus is stuck in us.
  3938. if ( IsDoorBlocked() && IsDoorClosing() )
  3939. {
  3940. m_hActivator = pActivator;
  3941. DoorOpen( m_hBlocker );
  3942. return;
  3943. }
  3944. else if ( IsDoorBlocked() && IsDoorOpening() )
  3945. {
  3946. m_hActivator = pActivator;
  3947. DoorClose();
  3948. return;
  3949. }
  3950. if (IsDoorClosed() || (IsDoorOpen() && HasSpawnFlags(SF_DOOR_USE_CLOSES)))
  3951. {
  3952. // Ready to be opened or closed.
  3953. if ( IsDoorLocked() )
  3954. {
  3955. int nSequence = SelectWeightedSequence((Activity)ACT_DOOR_LOCKED);
  3956. if ( nSequence >= 0 )
  3957. PropSetSequence( SelectWeightedSequence((Activity)ACT_DOOR_LOCKED) );
  3958. PlayLockSounds(this, &m_ls, TRUE, FALSE);
  3959. m_OnLockedUse.FireOutput( pActivator, pCaller );
  3960. }
  3961. else
  3962. {
  3963. m_hActivator = pActivator;
  3964. PlayLockSounds(this, &m_ls, FALSE, FALSE);
  3965. int nSequence = SelectWeightedSequence((Activity)ACT_DOOR_OPEN);
  3966. PropSetSequence( (nSequence >= 0 ) ? nSequence : GetSequence() );
  3967. if ((nSequence == -1) || !HasAnimEvent(nSequence, AE_DOOR_OPEN))
  3968. {
  3969. // No open anim event, we need to open the door here.
  3970. DoorActivate();
  3971. }
  3972. }
  3973. }
  3974. else if ( IsDoorOpening() && HasSpawnFlags(SF_DOOR_USE_CLOSES) )
  3975. {
  3976. if ( IsDoorLocked( ) == false )
  3977. {
  3978. // We've been used while opening, close.
  3979. m_hActivator = pActivator;
  3980. DoorClose();
  3981. }
  3982. }
  3983. else if ( IsDoorClosing() || IsDoorAjar() )
  3984. {
  3985. if ( IsDoorLocked( ) == false )
  3986. {
  3987. m_hActivator = pActivator;
  3988. DoorOpen( m_hActivator );
  3989. }
  3990. }
  3991. }
  3992. //-----------------------------------------------------------------------------
  3993. // Purpose: Closes the door if it is not already closed.
  3994. //-----------------------------------------------------------------------------
  3995. void CBasePropDoor::InputClose(inputdata_t &inputdata)
  3996. {
  3997. if (!IsDoorClosed())
  3998. {
  3999. m_OnClose.FireOutput(inputdata.pActivator, this);
  4000. DoorClose();
  4001. }
  4002. }
  4003. //-----------------------------------------------------------------------------
  4004. // Purpose: Input handler that locks the door.
  4005. //-----------------------------------------------------------------------------
  4006. void CBasePropDoor::InputLock(inputdata_t &inputdata)
  4007. {
  4008. Lock();
  4009. }
  4010. //-----------------------------------------------------------------------------
  4011. // Purpose: Opens the door if it is not already open.
  4012. //-----------------------------------------------------------------------------
  4013. void CBasePropDoor::InputOpen(inputdata_t &inputdata)
  4014. {
  4015. OpenIfUnlocked(inputdata.pActivator, NULL);
  4016. }
  4017. //-----------------------------------------------------------------------------
  4018. // Purpose: Opens the door away from a specified entity if it is not already open.
  4019. //-----------------------------------------------------------------------------
  4020. void CBasePropDoor::InputOpenAwayFrom(inputdata_t &inputdata)
  4021. {
  4022. CBaseEntity *pOpenAwayFrom = gEntList.FindEntityByName( NULL, inputdata.value.String(), NULL, inputdata.pActivator, inputdata.pCaller );
  4023. OpenIfUnlocked(inputdata.pActivator, pOpenAwayFrom);
  4024. }
  4025. //-----------------------------------------------------------------------------
  4026. // Purpose:
  4027. //
  4028. // FIXME: This function should be combined with DoorOpen, but doing that
  4029. // could break existing content. Fix after shipping!
  4030. //
  4031. // Input : *pOpenAwayFrom -
  4032. //-----------------------------------------------------------------------------
  4033. void CBasePropDoor::OpenIfUnlocked(CBaseEntity *pActivator, CBaseEntity *pOpenAwayFrom)
  4034. {
  4035. // I'm locked, can't open
  4036. if ( IsDoorLocked() )
  4037. return;
  4038. if (!IsDoorOpen() && !IsDoorOpening())
  4039. {
  4040. // Play door unlock sounds.
  4041. PlayLockSounds(this, &m_ls, false, false);
  4042. m_OnOpen.FireOutput(pActivator, this);
  4043. DoorOpen(pOpenAwayFrom);
  4044. }
  4045. }
  4046. //-----------------------------------------------------------------------------
  4047. // Purpose: Opens the door if it is not already open.
  4048. //-----------------------------------------------------------------------------
  4049. void CBasePropDoor::InputToggle(inputdata_t &inputdata)
  4050. {
  4051. if (IsDoorClosed())
  4052. {
  4053. // I'm locked, can't open
  4054. if ( IsDoorLocked() )
  4055. return;
  4056. DoorOpen(NULL);
  4057. }
  4058. else if (IsDoorOpen())
  4059. {
  4060. DoorClose();
  4061. }
  4062. }
  4063. //-----------------------------------------------------------------------------
  4064. // Purpose: Input handler that unlocks the door.
  4065. //-----------------------------------------------------------------------------
  4066. void CBasePropDoor::InputUnlock(inputdata_t &inputdata)
  4067. {
  4068. Unlock();
  4069. }
  4070. //-----------------------------------------------------------------------------
  4071. // Purpose: Locks the door so that it cannot be opened.
  4072. //-----------------------------------------------------------------------------
  4073. void CBasePropDoor::Lock(void)
  4074. {
  4075. m_bLocked = true;
  4076. }
  4077. //-----------------------------------------------------------------------------
  4078. // Purpose: Unlocks the door so that it can be opened.
  4079. //-----------------------------------------------------------------------------
  4080. void CBasePropDoor::Unlock(void)
  4081. {
  4082. if (!m_nHardwareType)
  4083. {
  4084. // Doors with no hardware must always be locked.
  4085. DevWarning(1, "Unlocking prop_door '%s' at (%.0f %.0f %.0f) with no hardware. All openable doors must have hardware!\n", GetDebugName(), GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z);
  4086. }
  4087. m_bLocked = false;
  4088. }
  4089. //-----------------------------------------------------------------------------
  4090. // Purpose: Causes the door to "do its thing", i.e. start moving, and cascade activation.
  4091. //-----------------------------------------------------------------------------
  4092. bool CBasePropDoor::DoorActivate( void )
  4093. {
  4094. if ( IsDoorOpen() && DoorCanClose( false ) )
  4095. {
  4096. DoorClose();
  4097. }
  4098. else
  4099. {
  4100. DoorOpen( m_hActivator );
  4101. }
  4102. return true;
  4103. }
  4104. //-----------------------------------------------------------------------------
  4105. // Purpose: Starts the door opening.
  4106. //-----------------------------------------------------------------------------
  4107. void CBasePropDoor::DoorOpen(CBaseEntity *pOpenAwayFrom)
  4108. {
  4109. // Don't bother if we're already doing this
  4110. if ( IsDoorOpen() || IsDoorOpening() )
  4111. return;
  4112. UpdateAreaPortals(true);
  4113. // It could be going-down, if blocked.
  4114. ASSERT( IsDoorClosed() || IsDoorClosing() || IsDoorAjar() );
  4115. // Emit door moving and stop sounds on CHAN_STATIC so that the multicast doesn't
  4116. // filter them out and leave a client stuck with looping door sounds!
  4117. if (!HasSpawnFlags(SF_DOOR_SILENT))
  4118. {
  4119. EmitSound( STRING( m_SoundMoving ) );
  4120. if ( m_hActivator && m_hActivator->IsPlayer() && !HasSpawnFlags( SF_DOOR_SILENT_TO_NPCS ) )
  4121. {
  4122. CSoundEnt::InsertSound( SOUND_PLAYER, GetAbsOrigin(), 512, 0.5, this );//<<TODO>>//magic number
  4123. }
  4124. }
  4125. SetDoorState( DOOR_STATE_OPENING );
  4126. SetMoveDone(&CBasePropDoor::DoorOpenMoveDone);
  4127. // Virtual function that starts the door moving for whatever type of door this is.
  4128. BeginOpening(pOpenAwayFrom);
  4129. m_OnOpen.FireOutput(this, this);
  4130. // Tell all the slaves
  4131. if ( HasSlaves() )
  4132. {
  4133. int numDoors = m_hDoorList.Count();
  4134. CBasePropDoor *pLinkedDoor = NULL;
  4135. // Open all linked doors
  4136. for ( int i = 0; i < numDoors; i++ )
  4137. {
  4138. pLinkedDoor = m_hDoorList[i];
  4139. if ( pLinkedDoor != NULL )
  4140. {
  4141. // If the door isn't already moving, get it moving
  4142. pLinkedDoor->m_hActivator = m_hActivator;
  4143. pLinkedDoor->DoorOpen( pOpenAwayFrom );
  4144. }
  4145. }
  4146. }
  4147. }
  4148. //-----------------------------------------------------------------------------
  4149. // Purpose: The door has reached the open position. Either close automatically
  4150. // or wait for another activation.
  4151. //-----------------------------------------------------------------------------
  4152. void CBasePropDoor::DoorOpenMoveDone(void)
  4153. {
  4154. SetDoorBlocker( NULL );
  4155. if (!HasSpawnFlags(SF_DOOR_SILENT))
  4156. {
  4157. EmitSound( STRING( m_SoundOpen ) );
  4158. }
  4159. ASSERT(IsDoorOpening());
  4160. SetDoorState( DOOR_STATE_OPEN );
  4161. if (WillAutoReturn())
  4162. {
  4163. // In flWait seconds, DoorClose will fire, unless wait is -1, then door stays open
  4164. SetMoveDoneTime(m_flAutoReturnDelay + 0.1);
  4165. SetMoveDone(&CBasePropDoor::DoorAutoCloseThink);
  4166. if (m_flAutoReturnDelay == -1)
  4167. {
  4168. SetNextThink( TICK_NEVER_THINK );
  4169. }
  4170. }
  4171. CAI_BaseNPC *pNPC = dynamic_cast<CAI_BaseNPC *>(m_hActivator.Get());
  4172. if (pNPC)
  4173. {
  4174. // Notify the NPC that opened us.
  4175. pNPC->OnDoorFullyOpen(this);
  4176. }
  4177. m_OnFullyOpen.FireOutput(this, this);
  4178. // Let the leaf class do its thing.
  4179. OnDoorOpened();
  4180. m_hActivator = NULL;
  4181. }
  4182. //-----------------------------------------------------------------------------
  4183. // Purpose: Think function that tries to close the door. Used for autoreturn.
  4184. //-----------------------------------------------------------------------------
  4185. void CBasePropDoor::DoorAutoCloseThink(void)
  4186. {
  4187. // When autoclosing, we check both sides so that we don't close in the player's
  4188. // face, or in an NPC's face for that matter, because they might be shooting
  4189. // through the doorway.
  4190. if ( !DoorCanClose( true ) )
  4191. {
  4192. if (m_flAutoReturnDelay == -1)
  4193. {
  4194. SetNextThink( TICK_NEVER_THINK );
  4195. }
  4196. else
  4197. {
  4198. // In flWait seconds, DoorClose will fire, unless wait is -1, then door stays open
  4199. SetMoveDoneTime(m_flAutoReturnDelay + 0.1);
  4200. SetMoveDone(&CBasePropDoor::DoorAutoCloseThink);
  4201. }
  4202. return;
  4203. }
  4204. DoorClose();
  4205. }
  4206. //-----------------------------------------------------------------------------
  4207. // Purpose: Starts the door closing.
  4208. //-----------------------------------------------------------------------------
  4209. void CBasePropDoor::DoorClose(void)
  4210. {
  4211. // Don't bother if we're already doing this
  4212. if ( IsDoorClosed() || IsDoorClosing() )
  4213. return;
  4214. if (!HasSpawnFlags(SF_DOOR_SILENT))
  4215. {
  4216. EmitSound( STRING( m_SoundMoving ) );
  4217. if ( m_hActivator && m_hActivator->IsPlayer() )
  4218. {
  4219. CSoundEnt::InsertSound( SOUND_PLAYER, GetAbsOrigin(), 512, 0.5, this );//<<TODO>>//magic number
  4220. }
  4221. }
  4222. ASSERT(IsDoorOpen() || IsDoorOpening());
  4223. SetDoorState( DOOR_STATE_CLOSING );
  4224. SetMoveDone(&CBasePropDoor::DoorCloseMoveDone);
  4225. // This will set the movedone time.
  4226. BeginClosing();
  4227. m_OnClose.FireOutput(this, this);
  4228. // Tell all the slaves
  4229. if ( HasSlaves() )
  4230. {
  4231. int numDoors = m_hDoorList.Count();
  4232. CBasePropDoor *pLinkedDoor = NULL;
  4233. // Open all linked doors
  4234. for ( int i = 0; i < numDoors; i++ )
  4235. {
  4236. pLinkedDoor = m_hDoorList[i];
  4237. if ( pLinkedDoor != NULL )
  4238. {
  4239. // If the door isn't already moving, get it moving
  4240. pLinkedDoor->DoorClose();
  4241. }
  4242. }
  4243. }
  4244. }
  4245. //-----------------------------------------------------------------------------
  4246. // Purpose: The door has reached the closed position. Return to quiescence.
  4247. //-----------------------------------------------------------------------------
  4248. void CBasePropDoor::DoorCloseMoveDone(void)
  4249. {
  4250. SetDoorBlocker( NULL );
  4251. if (!HasSpawnFlags(SF_DOOR_SILENT))
  4252. {
  4253. StopSound( STRING( m_SoundMoving ) );
  4254. EmitSound( STRING( m_SoundClose ) );
  4255. }
  4256. ASSERT(IsDoorClosing());
  4257. SetDoorState( DOOR_STATE_CLOSED );
  4258. m_OnFullyClosed.FireOutput(m_hActivator, this);
  4259. // Close the area portals just after the door closes, to prevent visual artifacts in multiplayer games
  4260. SetContextThink( &CBasePropDoor::DisableAreaPortalThink, gpGlobals->curtime + 0.5f, "AreaPortal" );
  4261. // Let the leaf class do its thing.
  4262. OnDoorClosed();
  4263. m_hActivator = NULL;
  4264. }
  4265. //-----------------------------------------------------------------------------
  4266. // Purpose:
  4267. // Input : *pOther -
  4268. //-----------------------------------------------------------------------------
  4269. void CBasePropDoor::MasterStartBlocked( CBaseEntity *pOther )
  4270. {
  4271. if ( HasSlaves() )
  4272. {
  4273. int numDoors = m_hDoorList.Count();
  4274. CBasePropDoor *pLinkedDoor = NULL;
  4275. // Open all linked doors
  4276. for ( int i = 0; i < numDoors; i++ )
  4277. {
  4278. pLinkedDoor = m_hDoorList[i];
  4279. if ( pLinkedDoor != NULL )
  4280. {
  4281. // If the door isn't already moving, get it moving
  4282. pLinkedDoor->OnStartBlocked( pOther );
  4283. }
  4284. }
  4285. }
  4286. // Start ourselves blocked
  4287. OnStartBlocked( pOther );
  4288. }
  4289. //-----------------------------------------------------------------------------
  4290. // Purpose: Called the first frame that the door is blocked while opening or closing.
  4291. // Input : pOther - The blocking entity.
  4292. //-----------------------------------------------------------------------------
  4293. void CBasePropDoor::StartBlocked( CBaseEntity *pOther )
  4294. {
  4295. m_bFirstBlocked = true;
  4296. if ( GetMaster() != NULL )
  4297. {
  4298. GetMaster()->MasterStartBlocked( pOther );
  4299. return;
  4300. }
  4301. // Start ourselves blocked
  4302. OnStartBlocked( pOther );
  4303. }
  4304. //-----------------------------------------------------------------------------
  4305. // Purpose:
  4306. // Input : *pOther -
  4307. //-----------------------------------------------------------------------------
  4308. void CBasePropDoor::OnStartBlocked( CBaseEntity *pOther )
  4309. {
  4310. if ( m_bFirstBlocked == false )
  4311. {
  4312. DoorStop();
  4313. }
  4314. SetDoorBlocker( pOther );
  4315. if (!HasSpawnFlags(SF_DOOR_SILENT))
  4316. {
  4317. StopSound( STRING( m_SoundMoving ) );
  4318. }
  4319. //
  4320. // Fire whatever events we need to due to our blocked state.
  4321. //
  4322. if (IsDoorClosing())
  4323. {
  4324. // Closed into an NPC, open.
  4325. if ( pOther->MyNPCPointer() )
  4326. {
  4327. DoorOpen( pOther );
  4328. }
  4329. m_OnBlockedClosing.FireOutput(pOther, this);
  4330. }
  4331. else
  4332. {
  4333. // Opened into an NPC, close.
  4334. if ( pOther->MyNPCPointer() )
  4335. {
  4336. DoorClose();
  4337. }
  4338. CAI_BaseNPC *pNPC = dynamic_cast<CAI_BaseNPC *>(m_hActivator.Get());
  4339. if ( pNPC != NULL )
  4340. {
  4341. // Notify the NPC that tried to open us.
  4342. pNPC->OnDoorBlocked( this );
  4343. }
  4344. m_OnBlockedOpening.FireOutput( pOther, this );
  4345. }
  4346. }
  4347. //-----------------------------------------------------------------------------
  4348. // Purpose: Called every frame when the door is blocked while opening or closing.
  4349. // Input : pOther - The blocking entity.
  4350. //-----------------------------------------------------------------------------
  4351. void CBasePropDoor::Blocked(CBaseEntity *pOther)
  4352. {
  4353. // dvs: TODO: will prop_door apply any blocking damage?
  4354. // Hurt the blocker a little.
  4355. //if (m_flBlockDamage)
  4356. //{
  4357. // pOther->TakeDamage(CTakeDamageInfo(this, this, m_flBlockDamage, DMG_CRUSH));
  4358. //}
  4359. if ( m_bForceClosed && ( pOther->GetMoveType() == MOVETYPE_VPHYSICS ) &&
  4360. ( pOther->m_takedamage == DAMAGE_NO || pOther->m_takedamage == DAMAGE_EVENTS_ONLY ) )
  4361. {
  4362. EntityPhysics_CreateSolver( this, pOther, true, 4.0f );
  4363. }
  4364. else if ( m_bForceClosed && ( pOther->GetMoveType() == MOVETYPE_VPHYSICS ) && ( pOther->m_takedamage == DAMAGE_YES ) )
  4365. {
  4366. pOther->TakeDamage( CTakeDamageInfo( this, this, pOther->GetHealth(), DMG_CRUSH ) );
  4367. }
  4368. // If we're set to force ourselves closed, keep going
  4369. if ( m_bForceClosed )
  4370. return;
  4371. // If a door has a negative wait, it would never come back if blocked,
  4372. // so let it just squash the object to death real fast.
  4373. // if (m_flAutoReturnDelay >= 0)
  4374. // {
  4375. // if (IsDoorClosing())
  4376. // {
  4377. // DoorOpen();
  4378. // }
  4379. // else
  4380. // {
  4381. // DoorClose();
  4382. // }
  4383. // }
  4384. // Block all door pieces with the same targetname here.
  4385. // if (GetEntityName() != NULL_STRING)
  4386. // {
  4387. // CBaseEntity pTarget = NULL;
  4388. // for (;;)
  4389. // {
  4390. // pTarget = gEntList.FindEntityByName(pTarget, GetEntityName() );
  4391. //
  4392. // if (pTarget != this)
  4393. // {
  4394. // if (!pTarget)
  4395. // break;
  4396. //
  4397. // if (FClassnameIs(pTarget, "prop_door_rotating"))
  4398. // {
  4399. // CPropDoorRotating *pDoor = (CPropDoorRotating *)pTarget;
  4400. //
  4401. // if (pDoor->m_fAutoReturnDelay >= 0)
  4402. // {
  4403. // if (pDoor->GetAbsVelocity() == GetAbsVelocity() && pDoor->GetLocalAngularVelocity() == GetLocalAngularVelocity())
  4404. // {
  4405. // // this is the most hacked, evil, bastardized thing I've ever seen. kjb
  4406. // if (FClassnameIs(pTarget, "prop_door_rotating"))
  4407. // {
  4408. // // set angles to realign rotating doors
  4409. // pDoor->SetLocalAngles(GetLocalAngles());
  4410. // pDoor->SetLocalAngularVelocity(vec3_angle);
  4411. // }
  4412. // else
  4413. // //{
  4414. // // // set origin to realign normal doors
  4415. // // pDoor->SetLocalOrigin(GetLocalOrigin());
  4416. // // pDoor->SetAbsVelocity(vec3_origin);// stop!
  4417. // //}
  4418. // }
  4419. //
  4420. // if (IsDoorClosing())
  4421. // {
  4422. // pDoor->DoorOpen();
  4423. // }
  4424. // else
  4425. // {
  4426. // pDoor->DoorClose();
  4427. // }
  4428. // }
  4429. // }
  4430. // }
  4431. // }
  4432. // }
  4433. }
  4434. //-----------------------------------------------------------------------------
  4435. // Purpose: Called the first frame that the door is unblocked while opening or closing.
  4436. //-----------------------------------------------------------------------------
  4437. void CBasePropDoor::EndBlocked( void )
  4438. {
  4439. if ( GetMaster() != NULL )
  4440. {
  4441. GetMaster()->EndBlocked();
  4442. return;
  4443. }
  4444. if ( HasSlaves() )
  4445. {
  4446. int numDoors = m_hDoorList.Count();
  4447. CBasePropDoor *pLinkedDoor = NULL;
  4448. // Check all links as well
  4449. for ( int i = 0; i < numDoors; i++ )
  4450. {
  4451. pLinkedDoor = m_hDoorList[i];
  4452. if ( pLinkedDoor != NULL )
  4453. {
  4454. // Make sure they can close as well
  4455. pLinkedDoor->OnEndBlocked();
  4456. }
  4457. }
  4458. }
  4459. // Emit door moving and stop sounds on CHAN_STATIC so that the multicast doesn't
  4460. // filter them out and leave a client stuck with looping door sounds!
  4461. if (!HasSpawnFlags(SF_DOOR_SILENT))
  4462. {
  4463. EmitSound( STRING( m_SoundMoving ) );
  4464. }
  4465. //
  4466. // Fire whatever events we need to due to our unblocked state.
  4467. //
  4468. if (IsDoorClosing())
  4469. {
  4470. m_OnUnblockedClosing.FireOutput(this, this);
  4471. }
  4472. else
  4473. {
  4474. m_OnUnblockedOpening.FireOutput(this, this);
  4475. }
  4476. OnEndBlocked();
  4477. }
  4478. //-----------------------------------------------------------------------------
  4479. // Purpose:
  4480. //-----------------------------------------------------------------------------
  4481. void CBasePropDoor::OnEndBlocked( void )
  4482. {
  4483. if ( m_bFirstBlocked )
  4484. return;
  4485. // Restart us going
  4486. DoorResume();
  4487. }
  4488. //-----------------------------------------------------------------------------
  4489. // Purpose:
  4490. // Input : *pNPC -
  4491. //-----------------------------------------------------------------------------
  4492. bool CBasePropDoor::NPCOpenDoor( CAI_BaseNPC *pNPC )
  4493. {
  4494. // dvs: TODO: use activator filter here
  4495. // dvs: TODO: outboard entity containing rules for whether door is operable?
  4496. if ( IsDoorClosed() )
  4497. {
  4498. // Use the door
  4499. Use( pNPC, pNPC, USE_ON, 0 );
  4500. }
  4501. return true;
  4502. }
  4503. bool CBasePropDoor::TestCollision( const Ray_t &ray, unsigned int mask, trace_t& trace )
  4504. {
  4505. if ( !VPhysicsGetObject() )
  4506. return false;
  4507. MDLCACHE_CRITICAL_SECTION();
  4508. CStudioHdr *pStudioHdr = GetModelPtr( );
  4509. if (!pStudioHdr)
  4510. return false;
  4511. if ( !( pStudioHdr->contents() & mask ) )
  4512. return false;
  4513. physcollision->TraceBox( ray, VPhysicsGetObject()->GetCollide(), GetAbsOrigin(), GetAbsAngles(), &trace );
  4514. if ( trace.DidHit() )
  4515. {
  4516. trace.contents = pStudioHdr->contents();
  4517. // use the default surface properties
  4518. trace.surface.name = "**studio**";
  4519. trace.surface.flags = 0;
  4520. trace.surface.surfaceProps = VPhysicsGetObject()->GetMaterialIndex();
  4521. return true;
  4522. }
  4523. return false;
  4524. }
  4525. //-----------------------------------------------------------------------------
  4526. // Custom trace filter for doors
  4527. // Will only test against entities and rejects physics objects below a mass threshold
  4528. //-----------------------------------------------------------------------------
  4529. class CTraceFilterDoor : public CTraceFilterEntitiesOnly
  4530. {
  4531. public:
  4532. // It does have a base, but we'll never network anything below here..
  4533. DECLARE_CLASS_NOBASE( CTraceFilterDoor );
  4534. CTraceFilterDoor( const IHandleEntity *pDoor, const IHandleEntity *passentity, int collisionGroup )
  4535. : m_pDoor(pDoor), m_collisionGroup(collisionGroup)
  4536. {
  4537. if ( passentity )
  4538. {
  4539. m_pPassEnts.AddToTail( passentity );
  4540. }
  4541. }
  4542. virtual bool ShouldHitEntity( IHandleEntity *pHandleEntity, int contentsMask )
  4543. {
  4544. if ( !StandardFilterRules( pHandleEntity, contentsMask ) )
  4545. return false;
  4546. if ( !PassServerEntityFilter( pHandleEntity, m_pDoor ) )
  4547. return false;
  4548. for ( int i=0; i<m_pPassEnts.Count(); ++i )
  4549. {
  4550. if ( !PassServerEntityFilter( pHandleEntity, m_pPassEnts[i] ) )
  4551. return false;
  4552. }
  4553. // Don't test if the game code tells us we should ignore this collision...
  4554. CBaseEntity *pEntity = EntityFromEntityHandle( pHandleEntity );
  4555. if ( pEntity )
  4556. {
  4557. // If this entity is parented to the door, then we don't want to collide with it.
  4558. const CBaseEntity *pDoorEntity = EntityFromEntityHandle( m_pDoor );
  4559. if ( pEntity->GetMoveParent() == pDoorEntity )
  4560. return false;
  4561. if ( !pEntity->ShouldCollide( m_collisionGroup, contentsMask ) )
  4562. return false;
  4563. if ( !g_pGameRules->ShouldCollide( m_collisionGroup, pEntity->GetCollisionGroup() ) )
  4564. return false;
  4565. // If objects are small enough and can move, close on them
  4566. if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
  4567. {
  4568. IPhysicsObject *pPhysics = pEntity->VPhysicsGetObject();
  4569. Assert(pPhysics);
  4570. // Must either be squashable or very light
  4571. if ( pPhysics->IsMoveable() && pPhysics->GetMass() < 32 )
  4572. return false;
  4573. }
  4574. }
  4575. return true;
  4576. }
  4577. void AddPassEnt( CBaseEntity *pEntity )
  4578. {
  4579. m_pPassEnts.AddToTail( pEntity );
  4580. }
  4581. private:
  4582. CUtlVector< const IHandleEntity * > m_pPassEnts;
  4583. const IHandleEntity *m_pDoor;
  4584. int m_collisionGroup;
  4585. };
  4586. inline void TraceHull_Door( const CBasePropDoor *pDoor, const Vector &vecAbsStart, const Vector &vecAbsEnd, const Vector &hullMin,
  4587. const Vector &hullMax, unsigned int mask, const CBaseEntity *ignore,
  4588. int collisionGroup, trace_t *ptr )
  4589. {
  4590. Ray_t ray;
  4591. ray.Init( vecAbsStart, vecAbsEnd, hullMin, hullMax );
  4592. CTraceFilterDoor traceFilter( pDoor, ignore, collisionGroup );
  4593. enginetrace->TraceRay( ray, mask, &traceFilter, ptr );
  4594. }
  4595. BEGIN_DATADESC(CPropDoorRotating)
  4596. DEFINE_KEYFIELD(m_eSpawnPosition, FIELD_INTEGER, "spawnpos"),
  4597. DEFINE_KEYFIELD(m_eOpenDirection, FIELD_INTEGER, "opendir" ),
  4598. DEFINE_KEYFIELD(m_vecAxis, FIELD_VECTOR, "axis"),
  4599. DEFINE_KEYFIELD(m_flDistance, FIELD_FLOAT, "distance"),
  4600. DEFINE_KEYFIELD( m_angRotationAjar, FIELD_VECTOR, "ajarangles" ),
  4601. DEFINE_FIELD( m_angRotationClosed, FIELD_VECTOR ),
  4602. DEFINE_FIELD( m_angRotationOpenForward, FIELD_VECTOR ),
  4603. DEFINE_FIELD( m_angRotationOpenBack, FIELD_VECTOR ),
  4604. DEFINE_FIELD( m_angGoal, FIELD_VECTOR ),
  4605. DEFINE_FIELD( m_hDoorBlocker, FIELD_EHANDLE ),
  4606. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetRotationDistance", InputSetRotationDistance ),
  4607. DEFINE_INPUTFUNC( FIELD_FLOAT, "MoveToRotationDistance", InputMoveToRotationDistance ),
  4608. DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeed", InputSetSpeed ),
  4609. DEFINE_OUTPUT( m_OnRotationDone, "OnRotationDone" ),
  4610. //m_vecForwardBoundsMin
  4611. //m_vecForwardBoundsMax
  4612. //m_vecBackBoundsMin
  4613. //m_vecBackBoundsMax
  4614. END_DATADESC()
  4615. IMPLEMENT_SERVERCLASS_ST( CPropDoorRotating, DT_PropDoorRotating )
  4616. END_SEND_TABLE()
  4617. // Experimenting with CPropDoorRotatingBreakable from L4D (KWD)
  4618. //LINK_ENTITY_TO_CLASS(prop_door_rotating, CPropDoorRotating);
  4619. //-----------------------------------------------------------------------------
  4620. // Destructor
  4621. //-----------------------------------------------------------------------------
  4622. CPropDoorRotating::~CPropDoorRotating( void )
  4623. {
  4624. // Remove our door blocker entity
  4625. if ( m_hDoorBlocker != NULL )
  4626. {
  4627. UTIL_Remove( m_hDoorBlocker );
  4628. }
  4629. }
  4630. //-----------------------------------------------------------------------------
  4631. // Purpose:
  4632. // Input : &mins1 -
  4633. // &maxs1 -
  4634. // &mins2 -
  4635. // &maxs2 -
  4636. // *destMins -
  4637. // *destMaxs -
  4638. //-----------------------------------------------------------------------------
  4639. void UTIL_ComputeAABBForBounds( const Vector &mins1, const Vector &maxs1, const Vector &mins2, const Vector &maxs2, Vector *destMins, Vector *destMaxs )
  4640. {
  4641. // Find the minimum extents
  4642. (*destMins)[0] = MIN( mins1[0], mins2[0] );
  4643. (*destMins)[1] = MIN( mins1[1], mins2[1] );
  4644. (*destMins)[2] = MIN( mins1[2], mins2[2] );
  4645. // Find the maximum extents
  4646. (*destMaxs)[0] = MAX( maxs1[0], maxs2[0] );
  4647. (*destMaxs)[1] = MAX( maxs1[1], maxs2[1] );
  4648. (*destMaxs)[2] = MAX( maxs1[2], maxs2[2] );
  4649. }
  4650. bool DoorUnlockedFilter( CBaseEntity *pVisibleEntity, CBasePlayer *pViewingPlayer )
  4651. {
  4652. CBasePropDoor *pDoor = static_cast<CBasePropDoor*>( pVisibleEntity );
  4653. if ( pDoor )
  4654. return !pDoor->IsDoorLocked();
  4655. return false;
  4656. }
  4657. //-----------------------------------------------------------------------------
  4658. // Purpose:
  4659. //-----------------------------------------------------------------------------
  4660. void CPropDoorRotating::Spawn()
  4661. {
  4662. // Doors are built closed, so save the current angles as the closed angles.
  4663. m_angRotationClosed = GetLocalAngles();
  4664. // The axis of rotation must be along the z axis for now.
  4665. // NOTE: If you change this, be sure to change IsHingeOnLeft to account for it!
  4666. m_vecAxis = Vector(0, 0, 1);
  4667. CalcOpenAngles();
  4668. // Call this last! It relies on stuff we calculated above.
  4669. BaseClass::Spawn();
  4670. // We have to call this after we call the base Spawn because it requires
  4671. // that the model already be set.
  4672. if ( IsHingeOnLeft() )
  4673. {
  4674. V_swap( m_angRotationOpenForward, m_angRotationOpenBack );
  4675. }
  4676. // Figure out our volumes of movement as this door opens
  4677. CalculateDoorVolume( GetLocalAngles(), m_angRotationOpenForward, &m_vecForwardBoundsMin, &m_vecForwardBoundsMax );
  4678. CalculateDoorVolume( GetLocalAngles(), m_angRotationOpenBack, &m_vecBackBoundsMin, &m_vecBackBoundsMax );
  4679. VisibilityMonitor_AddEntity( this, 600.0f, NULL, &DoorUnlockedFilter );
  4680. }
  4681. //-----------------------------------------------------------------------------
  4682. // Purpose: Setup the m_angRotationOpenForward and m_angRotationOpenBack variables based on
  4683. // the m_flDistance variable. Also restricts m_flDistance > 0.
  4684. //-----------------------------------------------------------------------------
  4685. void CPropDoorRotating::CalcOpenAngles()
  4686. {
  4687. // HACK: convert the axis of rotation to dPitch dYaw dRoll
  4688. Vector vecMoveDir(m_vecAxis.y, m_vecAxis.z, m_vecAxis.x);
  4689. if (m_flDistance == 0)
  4690. {
  4691. m_flDistance = 90;
  4692. }
  4693. m_flDistance = fabs(m_flDistance);
  4694. // Calculate our orientation when we are fully open.
  4695. m_angRotationOpenForward.x = m_angRotationClosed.x - (vecMoveDir.x * m_flDistance);
  4696. m_angRotationOpenForward.y = m_angRotationClosed.y - (vecMoveDir.y * m_flDistance);
  4697. m_angRotationOpenForward.z = m_angRotationClosed.z - (vecMoveDir.z * m_flDistance);
  4698. m_angRotationOpenBack.x = m_angRotationClosed.x + (vecMoveDir.x * m_flDistance);
  4699. m_angRotationOpenBack.y = m_angRotationClosed.y + (vecMoveDir.y * m_flDistance);
  4700. m_angRotationOpenBack.z = m_angRotationClosed.z + (vecMoveDir.z * m_flDistance);
  4701. }
  4702. //-----------------------------------------------------------------------------
  4703. // Figures out whether the door's hinge is on its left or its right.
  4704. // Assumes:
  4705. // - that the door is hinged through its origin.
  4706. // - that the origin is at one edge of the door (revolving doors will give
  4707. // a random answer)
  4708. // - that the hinge axis lies along the z axis
  4709. //-----------------------------------------------------------------------------
  4710. bool CPropDoorRotating::IsHingeOnLeft()
  4711. {
  4712. //
  4713. // Find the point farthest from the hinge in 2D.
  4714. //
  4715. Vector vecMins;
  4716. Vector vecMaxs;
  4717. CollisionProp()->WorldSpaceAABB( &vecMins, &vecMaxs );
  4718. vecMins -= GetAbsOrigin();
  4719. vecMaxs -= GetAbsOrigin();
  4720. // Throw out z -- we only care about 2D distance.
  4721. // NOTE: if we allow for arbitrary hinge axes, this needs to change
  4722. vecMins.z = vecMaxs.z = 0;
  4723. Vector vecPointCheck;
  4724. if ( vecMins.LengthSqr() > vecMaxs.LengthSqr() )
  4725. {
  4726. vecPointCheck = vecMins;
  4727. }
  4728. else
  4729. {
  4730. vecPointCheck = vecMaxs;
  4731. }
  4732. //
  4733. // See if the projection of that point lies along our right vector.
  4734. // If it does, the door is hinged on its left.
  4735. //
  4736. Vector vecRight;
  4737. GetVectors( NULL, &vecRight, NULL );
  4738. float flDot = DotProduct( vecPointCheck, vecRight );
  4739. return ( flDot > 0 );
  4740. }
  4741. //-----------------------------------------------------------------------------
  4742. // Purpose:
  4743. // Output : Returns true on success, false on failure.
  4744. //-----------------------------------------------------------------------------
  4745. doorCheck_e CPropDoorRotating::GetOpenState( void )
  4746. {
  4747. return ( m_angGoal == m_angRotationOpenForward ) ? DOOR_CHECK_FORWARD : DOOR_CHECK_BACKWARD;
  4748. }
  4749. //-----------------------------------------------------------------------------
  4750. // Purpose:
  4751. //-----------------------------------------------------------------------------
  4752. void CPropDoorRotating::OnDoorOpened( void )
  4753. {
  4754. if ( m_hDoorBlocker != NULL )
  4755. {
  4756. // Allow passage through this blocker while open
  4757. m_hDoorBlocker->AddSolidFlags( FSOLID_NOT_SOLID );
  4758. if ( g_debug_doors.GetBool() )
  4759. {
  4760. NDebugOverlay::Box( GetAbsOrigin(), m_hDoorBlocker->CollisionProp()->OBBMins(), m_hDoorBlocker->CollisionProp()->OBBMaxs(), 0, 255, 0, true, 1.0f );
  4761. }
  4762. }
  4763. }
  4764. //-----------------------------------------------------------------------------
  4765. // Purpose:
  4766. //-----------------------------------------------------------------------------
  4767. void CPropDoorRotating::OnDoorClosed( void )
  4768. {
  4769. BaseClass::OnDoorClosed();
  4770. if ( m_hDoorBlocker != NULL )
  4771. {
  4772. // Destroy the blocker that was preventing NPCs from getting in our way.
  4773. UTIL_Remove( m_hDoorBlocker );
  4774. if ( g_debug_doors.GetBool() )
  4775. {
  4776. NDebugOverlay::Box( GetAbsOrigin(), m_hDoorBlocker->CollisionProp()->OBBMins(), m_hDoorBlocker->CollisionProp()->OBBMaxs(), 0, 255, 0, true, 1.0f );
  4777. }
  4778. }
  4779. }
  4780. //-----------------------------------------------------------------------------
  4781. // Purpose: Returns whether the way is clear for the door to close.
  4782. // Input : state - Which sides to check, forward, backward, or both.
  4783. // Output : Returns true if the door can close, false if the way is blocked.
  4784. //-----------------------------------------------------------------------------
  4785. bool CPropDoorRotating::DoorCanClose( bool bAutoClose )
  4786. {
  4787. if ( GetMaster() != NULL )
  4788. return GetMaster()->DoorCanClose( bAutoClose );
  4789. // Check all slaves
  4790. if ( HasSlaves() )
  4791. {
  4792. int numDoors = m_hDoorList.Count();
  4793. CPropDoorRotating *pLinkedDoor = NULL;
  4794. // Check all links as well
  4795. for ( int i = 0; i < numDoors; i++ )
  4796. {
  4797. pLinkedDoor = dynamic_cast<CPropDoorRotating *>((CBasePropDoor *)m_hDoorList[i]);
  4798. if ( pLinkedDoor != NULL )
  4799. {
  4800. if ( !pLinkedDoor->CheckDoorClear( bAutoClose ? DOOR_CHECK_FULL : pLinkedDoor->GetOpenState() ) )
  4801. return false;
  4802. }
  4803. }
  4804. }
  4805. // See if our path of movement is clear to allow us to shut
  4806. return CheckDoorClear( bAutoClose ? DOOR_CHECK_FULL : GetOpenState() );
  4807. }
  4808. //-----------------------------------------------------------------------------
  4809. // Purpose:
  4810. // Input : closedAngles -
  4811. // openAngles -
  4812. // *destMins -
  4813. // *destMaxs -
  4814. //-----------------------------------------------------------------------------
  4815. void CPropDoorRotating::CalculateDoorVolume( QAngle closedAngles, QAngle openAngles, Vector *destMins, Vector *destMaxs )
  4816. {
  4817. // Save our current angles and move to our start angles
  4818. QAngle saveAngles = GetLocalAngles();
  4819. SetLocalAngles( closedAngles );
  4820. // Find our AABB at the closed state
  4821. Vector closedMins, closedMaxs;
  4822. CollisionProp()->WorldSpaceAABB( &closedMins, &closedMaxs );
  4823. SetLocalAngles( openAngles );
  4824. // Find our AABB at the open state
  4825. Vector openMins, openMaxs;
  4826. CollisionProp()->WorldSpaceAABB( &openMins, &openMaxs );
  4827. // Reset our angles to our starting angles
  4828. SetLocalAngles( saveAngles );
  4829. // Find the minimum extents
  4830. UTIL_ComputeAABBForBounds( closedMins, closedMaxs, openMins, openMaxs, destMins, destMaxs );
  4831. // Move this back into local space
  4832. *destMins -= GetAbsOrigin();
  4833. *destMaxs -= GetAbsOrigin();
  4834. }
  4835. //-----------------------------------------------------------------------------
  4836. // Purpose:
  4837. //-----------------------------------------------------------------------------
  4838. void CPropDoorRotating::OnRestore( void )
  4839. {
  4840. BaseClass::OnRestore();
  4841. // Figure out our volumes of movement as this door opens
  4842. CalculateDoorVolume( GetLocalAngles(), m_angRotationOpenForward, &m_vecForwardBoundsMin, &m_vecForwardBoundsMax );
  4843. CalculateDoorVolume( GetLocalAngles(), m_angRotationOpenBack, &m_vecBackBoundsMin, &m_vecBackBoundsMax );
  4844. }
  4845. // extent contains the volume encompassing open + closed states
  4846. void CPropDoorRotating::ComputeDoorExtent( Extent *extent, unsigned int extentType )
  4847. {
  4848. if ( !extent )
  4849. return;
  4850. if ( extentType & DOOR_EXTENT_CLOSED )
  4851. {
  4852. Extent closedExtent;
  4853. CalculateDoorVolume( m_angRotationClosed, m_angRotationClosed, &extent->lo, &extent->hi );
  4854. if ( extentType & DOOR_EXTENT_OPEN )
  4855. {
  4856. Extent openExtent;
  4857. UTIL_ComputeAABBForBounds( m_vecForwardBoundsMin, m_vecForwardBoundsMax, m_vecBackBoundsMin, m_vecBackBoundsMax, &openExtent.lo, &openExtent.hi );
  4858. extent->Encompass( openExtent );
  4859. }
  4860. }
  4861. else if ( extentType & DOOR_EXTENT_OPEN )
  4862. {
  4863. UTIL_ComputeAABBForBounds( m_vecForwardBoundsMin, m_vecForwardBoundsMax, m_vecBackBoundsMin, m_vecBackBoundsMax, &extent->lo, &extent->hi );
  4864. }
  4865. extent->lo += GetAbsOrigin();
  4866. extent->hi += GetAbsOrigin();
  4867. }
  4868. //-----------------------------------------------------------------------------
  4869. // Purpose:
  4870. // Input : forward -
  4871. // mask -
  4872. // Output : Returns true on success, false on failure.
  4873. //-----------------------------------------------------------------------------
  4874. bool CPropDoorRotating::CheckDoorClear( doorCheck_e state )
  4875. {
  4876. Vector moveMins;
  4877. Vector moveMaxs;
  4878. switch ( state )
  4879. {
  4880. case DOOR_CHECK_FORWARD:
  4881. moveMins = m_vecForwardBoundsMin;
  4882. moveMaxs = m_vecForwardBoundsMax;
  4883. break;
  4884. case DOOR_CHECK_BACKWARD:
  4885. moveMins = m_vecBackBoundsMin;
  4886. moveMaxs = m_vecBackBoundsMax;
  4887. break;
  4888. default:
  4889. case DOOR_CHECK_FULL:
  4890. UTIL_ComputeAABBForBounds( m_vecForwardBoundsMin, m_vecForwardBoundsMax, m_vecBackBoundsMin, m_vecBackBoundsMax, &moveMins, &moveMaxs );
  4891. break;
  4892. }
  4893. CBaseEntity *m_pActivator = GetActivator();
  4894. // If this is a slave door, use our master's activator
  4895. if ( GetMaster() && m_pActivator == NULL )
  4896. {
  4897. CPropDoorRotating *m_pMasterDoor = dynamic_cast<CPropDoorRotating *>(GetMaster());
  4898. if ( m_pMasterDoor )
  4899. m_pActivator = m_pMasterDoor->GetActivator();
  4900. }
  4901. // Look for blocking entities, ignoring ourselves and the entity that opened us.
  4902. trace_t tr;
  4903. TraceHull_Door( this, GetAbsOrigin(), GetAbsOrigin(), moveMins, moveMaxs, MASK_SOLID, m_pActivator, COLLISION_GROUP_NONE, &tr );
  4904. if ( tr.allsolid || tr.startsolid )
  4905. {
  4906. if ( g_debug_doors.GetBool() )
  4907. {
  4908. NDebugOverlay::Box( GetAbsOrigin(), moveMins, moveMaxs, 255, 0, 0, true, 10.0f );
  4909. if ( tr.m_pEnt )
  4910. {
  4911. NDebugOverlay::Box( tr.m_pEnt->GetAbsOrigin(), tr.m_pEnt->CollisionProp()->OBBMins(), tr.m_pEnt->CollisionProp()->OBBMaxs(), 220, 220, 0, true, 10.0f );
  4912. }
  4913. }
  4914. return false;
  4915. }
  4916. if ( g_debug_doors.GetBool() )
  4917. {
  4918. NDebugOverlay::Box( GetAbsOrigin(), moveMins, moveMaxs, 0, 255, 0, true, 10.0f );
  4919. }
  4920. return true;
  4921. }
  4922. //-----------------------------------------------------------------------------
  4923. // Purpose: Puts the door in its appropriate position for spawning.
  4924. //-----------------------------------------------------------------------------
  4925. void CPropDoorRotating::DoorTeleportToSpawnPosition()
  4926. {
  4927. QAngle angSpawn;
  4928. // The Start Open spawnflag trumps the choices field
  4929. if ( ( HasSpawnFlags( SF_DOOR_START_OPEN_OBSOLETE ) ) || ( m_eSpawnPosition == DOOR_SPAWN_OPEN_FORWARD ) )
  4930. {
  4931. angSpawn = m_angRotationOpenForward;
  4932. SetDoorState( DOOR_STATE_OPEN );
  4933. }
  4934. else if ( m_eSpawnPosition == DOOR_SPAWN_OPEN_BACK )
  4935. {
  4936. angSpawn = m_angRotationOpenBack;
  4937. SetDoorState( DOOR_STATE_OPEN );
  4938. }
  4939. else if ( m_eSpawnPosition == DOOR_SPAWN_CLOSED )
  4940. {
  4941. angSpawn = m_angRotationClosed;
  4942. SetDoorState( DOOR_STATE_CLOSED );
  4943. }
  4944. else if ( m_eSpawnPosition == DOOR_SPAWN_AJAR )
  4945. {
  4946. angSpawn = m_angRotationAjar;
  4947. SetDoorState( DOOR_STATE_AJAR );
  4948. }
  4949. else
  4950. {
  4951. // Bogus spawn position setting!
  4952. Assert( false );
  4953. angSpawn = m_angRotationClosed;
  4954. SetDoorState( DOOR_STATE_CLOSED );
  4955. }
  4956. SetLocalAngles( angSpawn );
  4957. // Doesn't relink; that's done in Spawn.
  4958. }
  4959. //-----------------------------------------------------------------------------
  4960. // Purpose: After rotating, set angle to exact final angle, call "move done" function.
  4961. //-----------------------------------------------------------------------------
  4962. void CPropDoorRotating::MoveDone()
  4963. {
  4964. SetLocalAngles(m_angGoal);
  4965. SetLocalAngularVelocity(vec3_angle);
  4966. SetMoveDoneTime(-1);
  4967. BaseClass::MoveDone();
  4968. m_OnRotationDone.FireOutput(this, this);
  4969. }
  4970. //-----------------------------------------------------------------------------
  4971. // Purpose: Calculate m_vecVelocity and m_flNextThink to reach vecDest from
  4972. // GetLocalOrigin() traveling at flSpeed. Just like LinearMove, but rotational.
  4973. // Input : vecDestAngle -
  4974. // flSpeed -
  4975. //-----------------------------------------------------------------------------
  4976. void CPropDoorRotating::AngularMove(const QAngle &vecDestAngle, float flSpeed)
  4977. {
  4978. ASSERTSZ(flSpeed != 0, "AngularMove: no speed is defined!");
  4979. m_angGoal = vecDestAngle;
  4980. // Already there?
  4981. if (vecDestAngle == GetLocalAngles())
  4982. {
  4983. MoveDone();
  4984. return;
  4985. }
  4986. // Set destdelta to the vector needed to move.
  4987. QAngle vecDestDelta = vecDestAngle - GetLocalAngles();
  4988. // Divide by speed to get time to reach dest
  4989. float flTravelTime = vecDestDelta.Length() / flSpeed;
  4990. // Call MoveDone when destination angles are reached.
  4991. SetMoveDoneTime(flTravelTime);
  4992. // Scale the destdelta vector by the time spent traveling to get velocity.
  4993. SetLocalAngularVelocity(vecDestDelta * (1.0 / flTravelTime));
  4994. }
  4995. //-----------------------------------------------------------------------------
  4996. // Purpose:
  4997. //-----------------------------------------------------------------------------
  4998. void CPropDoorRotating::BeginOpening(CBaseEntity *pOpenAwayFrom)
  4999. {
  5000. // Determine the direction to open.
  5001. QAngle angOpen = m_angRotationOpenForward;
  5002. doorCheck_e eDirCheck = DOOR_CHECK_FORWARD;
  5003. if ( m_eOpenDirection == DOOR_ROTATING_OPEN_FORWARD )
  5004. {
  5005. eDirCheck = DOOR_CHECK_FORWARD;
  5006. angOpen = m_angRotationOpenForward;
  5007. }
  5008. else if ( m_eOpenDirection == DOOR_ROTATING_OPEN_BACKWARD )
  5009. {
  5010. eDirCheck = DOOR_CHECK_BACKWARD;
  5011. angOpen = m_angRotationOpenBack;
  5012. }
  5013. else // Can open either direction, test to see which is appropriate
  5014. {
  5015. if (pOpenAwayFrom != NULL)
  5016. {
  5017. // Using cross product to determine which side the player is on,
  5018. // as well as which side "open forward" is on, so we can always try to
  5019. // open away from the player.
  5020. Vector vecForwardDoor = WorldSpaceCenter() - GetAbsOrigin();
  5021. vecForwardDoor.z = 0;
  5022. vecForwardDoor.NormalizeInPlace();
  5023. Vector vecToActivator = pOpenAwayFrom->GetAbsOrigin() - GetAbsOrigin();
  5024. vecToActivator.z = 0;
  5025. vecToActivator.NormalizeInPlace();
  5026. Vector vecActivatorCross = vecForwardDoor.Cross( vecToActivator );
  5027. bool isActivatorOnLeft = false;
  5028. if ( vecActivatorCross.z < 0 )
  5029. {
  5030. // activator is on the right of the door (looking from hinge across doorway)
  5031. }
  5032. else
  5033. {
  5034. // activator is on the left of the door (looking from hinge across doorway)
  5035. isActivatorOnLeft = true;
  5036. }
  5037. bool isOpenForwardOnLeft = false;
  5038. float forwardYaw = AngleNormalize( m_angRotationOpenForward[1] - GetLocalAngles()[1] );
  5039. if ( forwardYaw < 0 )
  5040. {
  5041. // opening forward is on the right of the door (looking from hinge across doorway)
  5042. }
  5043. else
  5044. {
  5045. // opening forward is on the left of the door (looking from hinge across doorway)
  5046. isOpenForwardOnLeft = true;
  5047. }
  5048. if ( isActivatorOnLeft == isOpenForwardOnLeft )
  5049. {
  5050. angOpen = m_angRotationOpenBack;
  5051. eDirCheck = DOOR_CHECK_BACKWARD;
  5052. }
  5053. }
  5054. // If player is opening us and we're opening away from them, and we'll be
  5055. // blocked if we open away from them, open toward them.
  5056. if (IsPlayerOpening() && (pOpenAwayFrom && pOpenAwayFrom->IsPlayer()) && !CheckDoorClear(eDirCheck))
  5057. {
  5058. if (eDirCheck == DOOR_CHECK_FORWARD)
  5059. {
  5060. angOpen = m_angRotationOpenBack;
  5061. eDirCheck = DOOR_CHECK_BACKWARD;
  5062. }
  5063. else
  5064. {
  5065. angOpen = m_angRotationOpenForward;
  5066. eDirCheck = DOOR_CHECK_FORWARD;
  5067. }
  5068. }
  5069. }
  5070. // Create the door blocker
  5071. Vector mins, maxs;
  5072. if ( eDirCheck == DOOR_CHECK_FORWARD )
  5073. {
  5074. mins = m_vecForwardBoundsMin;
  5075. maxs = m_vecForwardBoundsMax;
  5076. }
  5077. else
  5078. {
  5079. mins = m_vecBackBoundsMin;
  5080. maxs = m_vecBackBoundsMax;
  5081. }
  5082. if ( m_hDoorBlocker != NULL )
  5083. {
  5084. UTIL_Remove( m_hDoorBlocker );
  5085. }
  5086. // Create a blocking entity to keep random entities out of our movement path
  5087. m_hDoorBlocker = CEntityBlocker::Create( GetAbsOrigin(), mins, maxs, pOpenAwayFrom, false );
  5088. Vector volumeCenter = ((mins+maxs) * 0.5f) + GetAbsOrigin();
  5089. // Ignoring the Z
  5090. float volumeRadius = MAX( fabs(mins.x), maxs.x );
  5091. volumeRadius = MAX( volumeRadius, MAX( fabs(mins.y), maxs.y ) );
  5092. // Debug
  5093. if ( g_debug_doors.GetBool() )
  5094. {
  5095. NDebugOverlay::Cross3D( volumeCenter, -Vector(volumeRadius,volumeRadius,volumeRadius), Vector(volumeRadius,volumeRadius,volumeRadius), 255, 0, 0, true, 1.0f );
  5096. }
  5097. // Make respectful entities move away from our path
  5098. if( !HasSpawnFlags(SF_DOOR_SILENT_TO_NPCS) )
  5099. {
  5100. CSoundEnt::InsertSound( SOUND_MOVE_AWAY, volumeCenter, volumeRadius, 0.5f, pOpenAwayFrom );
  5101. }
  5102. // Do final setup
  5103. if ( m_hDoorBlocker != NULL )
  5104. {
  5105. // Only block NPCs
  5106. m_hDoorBlocker->SetCollisionGroup( COLLISION_GROUP_DOOR_BLOCKER );
  5107. // If we hit something while opening, just stay unsolid until we try again
  5108. if ( CheckDoorClear( eDirCheck ) == false )
  5109. {
  5110. m_hDoorBlocker->AddSolidFlags( FSOLID_NOT_SOLID );
  5111. }
  5112. if ( g_debug_doors.GetBool() )
  5113. {
  5114. NDebugOverlay::Box( GetAbsOrigin(), m_hDoorBlocker->CollisionProp()->OBBMins(), m_hDoorBlocker->CollisionProp()->OBBMaxs(), 255, 0, 0, true, 1.0f );
  5115. }
  5116. }
  5117. AngularMove(angOpen, m_flSpeed);
  5118. }
  5119. //-----------------------------------------------------------------------------
  5120. // Purpose:
  5121. //-----------------------------------------------------------------------------
  5122. void CPropDoorRotating::BeginClosing( void )
  5123. {
  5124. if ( m_hDoorBlocker != NULL )
  5125. {
  5126. // Become solid again unless we're already being blocked
  5127. if ( CheckDoorClear( GetOpenState() ) )
  5128. {
  5129. m_hDoorBlocker->RemoveSolidFlags( FSOLID_NOT_SOLID );
  5130. }
  5131. if ( g_debug_doors.GetBool() )
  5132. {
  5133. NDebugOverlay::Box( GetAbsOrigin(), m_hDoorBlocker->CollisionProp()->OBBMins(), m_hDoorBlocker->CollisionProp()->OBBMaxs(), 255, 0, 0, true, 1.0f );
  5134. }
  5135. }
  5136. Vector vecAbsMins, vecAbsMaxs;
  5137. CollisionProp()->WorldSpaceAABB( &vecAbsMins, &vecAbsMaxs );
  5138. AngularMove(m_angRotationClosed, m_flSpeed);
  5139. }
  5140. //-----------------------------------------------------------------------------
  5141. // Purpose:
  5142. //-----------------------------------------------------------------------------
  5143. void CPropDoorRotating::DoorStop( void )
  5144. {
  5145. SetLocalAngularVelocity( vec3_angle );
  5146. SetMoveDoneTime( -1 );
  5147. }
  5148. //-----------------------------------------------------------------------------
  5149. // Purpose: Restart a door moving that was temporarily paused
  5150. //-----------------------------------------------------------------------------
  5151. void CPropDoorRotating::DoorResume( void )
  5152. {
  5153. // Restart our angular movement
  5154. AngularMove( m_angGoal, m_flSpeed );
  5155. }
  5156. //-----------------------------------------------------------------------------
  5157. // Purpose:
  5158. // Input : vecMoveDir -
  5159. // opendata -
  5160. //-----------------------------------------------------------------------------
  5161. void CPropDoorRotating::GetNPCOpenData(CAI_BaseNPC *pNPC, opendata_t &opendata)
  5162. {
  5163. // dvs: TODO: finalize open position, direction, activity
  5164. Vector vecForward;
  5165. Vector vecRight;
  5166. AngleVectors(GetAbsAngles(), &vecForward, &vecRight, NULL);
  5167. //
  5168. // Figure out where the NPC should stand to open this door,
  5169. // and what direction they should face.
  5170. //
  5171. opendata.vecStandPos = GetAbsOrigin() - (vecRight * 24);
  5172. opendata.vecStandPos.z -= 54;
  5173. Vector vecNPCOrigin = pNPC->GetAbsOrigin();
  5174. if (pNPC->GetAbsOrigin().Dot(vecForward) > GetAbsOrigin().Dot(vecForward))
  5175. {
  5176. // In front of the door relative to the door's forward vector.
  5177. opendata.vecStandPos += vecForward * 64;
  5178. opendata.vecFaceDir = -vecForward;
  5179. }
  5180. else
  5181. {
  5182. // Behind the door relative to the door's forward vector.
  5183. opendata.vecStandPos -= vecForward * 64;
  5184. opendata.vecFaceDir = vecForward;
  5185. }
  5186. opendata.eActivity = ACT_OPEN_DOOR;
  5187. }
  5188. //-----------------------------------------------------------------------------
  5189. // Purpose: Returns how long it will take this door to open.
  5190. //-----------------------------------------------------------------------------
  5191. float CPropDoorRotating::GetOpenInterval()
  5192. {
  5193. // set destdelta to the vector needed to move
  5194. QAngle vecDestDelta = m_angRotationOpenForward - GetLocalAngles();
  5195. // divide by speed to get time to reach dest
  5196. return vecDestDelta.Length() / m_flSpeed;
  5197. }
  5198. //-----------------------------------------------------------------------------
  5199. // Purpose: Draw any debug text overlays
  5200. // Output : Current text offset from the top
  5201. //-----------------------------------------------------------------------------
  5202. int CPropDoorRotating::DrawDebugTextOverlays(void)
  5203. {
  5204. int text_offset = BaseClass::DrawDebugTextOverlays();
  5205. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  5206. {
  5207. char tempstr[512];
  5208. Q_snprintf(tempstr, sizeof(tempstr),"Avelocity: %.2f %.2f %.2f", GetLocalAngularVelocity().x, GetLocalAngularVelocity().y, GetLocalAngularVelocity().z);
  5209. EntityText( text_offset, tempstr, 0);
  5210. text_offset++;
  5211. if ( IsDoorLocked() )
  5212. {
  5213. EntityText( text_offset, "LOCKED", 0);
  5214. text_offset++;
  5215. }
  5216. if ( IsDoorOpen() )
  5217. {
  5218. Q_strncpy(tempstr, "DOOR STATE: OPEN", sizeof(tempstr));
  5219. }
  5220. else if ( IsDoorClosed() )
  5221. {
  5222. Q_strncpy(tempstr, "DOOR STATE: CLOSED", sizeof(tempstr));
  5223. }
  5224. else if ( IsDoorOpening() )
  5225. {
  5226. Q_strncpy(tempstr, "DOOR STATE: OPENING", sizeof(tempstr));
  5227. }
  5228. else if ( IsDoorClosing() )
  5229. {
  5230. Q_strncpy(tempstr, "DOOR STATE: CLOSING", sizeof(tempstr));
  5231. }
  5232. else if ( IsDoorAjar() )
  5233. {
  5234. Q_strncpy(tempstr, "DOOR STATE: AJAR", sizeof(tempstr));
  5235. }
  5236. EntityText( text_offset, tempstr, 0);
  5237. text_offset++;
  5238. }
  5239. return text_offset;
  5240. }
  5241. //-----------------------------------------------------------------------------
  5242. // Purpose: Change this door's distance (in degrees) between open and closed
  5243. //-----------------------------------------------------------------------------
  5244. void CPropDoorRotating::InputSetRotationDistance( inputdata_t &inputdata )
  5245. {
  5246. m_flDistance = inputdata.value.Float();
  5247. // Recalculate our open volume
  5248. CalcOpenAngles();
  5249. CalculateDoorVolume( GetLocalAngles(), m_angRotationOpenForward, &m_vecForwardBoundsMin, &m_vecForwardBoundsMax );
  5250. CalculateDoorVolume( GetLocalAngles(), m_angRotationOpenBack, &m_vecBackBoundsMin, &m_vecBackBoundsMax );
  5251. }
  5252. //-----------------------------------------------------------------------------
  5253. // Purpose: Change this door's distance (in degrees) between open and closed and moves to the open position
  5254. //-----------------------------------------------------------------------------
  5255. void CPropDoorRotating::InputMoveToRotationDistance( inputdata_t &inputdata )
  5256. {
  5257. InputSetRotationDistance( inputdata );
  5258. BeginOpening(NULL);
  5259. }
  5260. // Debug sphere
  5261. class CPhysSphere : public CPhysicsProp
  5262. {
  5263. DECLARE_CLASS( CPhysSphere, CPhysicsProp );
  5264. public:
  5265. virtual bool OverridePropdata() { return true; }
  5266. bool CreateVPhysics()
  5267. {
  5268. SetSolid( SOLID_BBOX );
  5269. SetCollisionBounds( -Vector(12,12,12), Vector(12,12,12) );
  5270. objectparams_t params = g_PhysDefaultObjectParams;
  5271. params.pGameData = static_cast<void *>(this);
  5272. IPhysicsObject *pPhysicsObject = physenv->CreateSphereObject( 12, 0, GetAbsOrigin(), GetAbsAngles(), &params, false );
  5273. if ( pPhysicsObject )
  5274. {
  5275. VPhysicsSetObject( pPhysicsObject );
  5276. SetMoveType( MOVETYPE_VPHYSICS );
  5277. pPhysicsObject->Wake();
  5278. }
  5279. return true;
  5280. }
  5281. };
  5282. void CPropDoorRotating::InputSetSpeed(inputdata_t &inputdata)
  5283. {
  5284. AssertMsg1(inputdata.value.Float() > 0.0f, "InputSetSpeed on %s called with negative parameter!", GetDebugName() );
  5285. m_flSpeed = inputdata.value.Float();
  5286. DoorResume();
  5287. }
  5288. BEGIN_DATADESC(CCSPropExplodingBarrel)
  5289. DEFINE_THINKFUNC( FadeOut ),
  5290. DEFINE_THINKFUNC( StopParticle ),
  5291. END_DATADESC()
  5292. LINK_ENTITY_TO_CLASS(prop_exploding_barrel, CCSPropExplodingBarrel);
  5293. #define EXPLODING_BARREL_MODEL_WHOLE "models/props/coop_cementplant/exloding_barrel/exploding_barrel.mdl"
  5294. #define EXPLODING_BARREL_MODEL_TOP "models/props/coop_cementplant/exloding_barrel/exploding_barrel_top.mdl"
  5295. #define EXPLODING_BARREL_MODEL_BOTTOM "models/props/coop_cementplant/exloding_barrel/exploding_barrel_bottom.mdl"
  5296. #define EXPLODING_BARREL_EXPLODE_SND1 "BaseGrenade.Explode"
  5297. #define EXPLODING_BARREL_EXPLODE_SND2 "Inferno.Start_IncGrenade"
  5298. //--------------------------------------------------------------------------------------------------------
  5299. void CCSPropExplodingBarrel::Precache( void )
  5300. {
  5301. BaseClass::Precache();
  5302. PrecacheModel( EXPLODING_BARREL_MODEL_WHOLE );
  5303. PrecacheModel( EXPLODING_BARREL_MODEL_TOP );
  5304. PrecacheModel( EXPLODING_BARREL_MODEL_BOTTOM );
  5305. PrecacheSound( EXPLODING_BARREL_EXPLODE_SND1 );
  5306. PrecacheSound( EXPLODING_BARREL_EXPLODE_SND2 );
  5307. PrecacheParticleSystem( "explosion_hegrenade_interior" );
  5308. PrecacheParticleSystem( "dust_burning_engine" );
  5309. }
  5310. //--------------------------------------------------------------------------------------------------------
  5311. void CCSPropExplodingBarrel::Spawn( void )
  5312. {
  5313. SetModelName( MAKE_STRING(EXPLODING_BARREL_MODEL_WHOLE) );
  5314. BaseClass::Spawn();
  5315. SetModel( EXPLODING_BARREL_MODEL_WHOLE );
  5316. m_bExploded = false;
  5317. //PrecacheBreakables();
  5318. m_bAwake = true;
  5319. IPhysicsObject *pPhysics = VPhysicsGetObject();
  5320. if ( pPhysics )
  5321. {
  5322. pPhysics->EnableMotion( false );
  5323. }
  5324. m_iHealth = 10;
  5325. }
  5326. //--------------------------------------------------------------------------------------------------------
  5327. void CCSPropExplodingBarrel::Event_Killed( const CTakeDamageInfo &info )
  5328. {
  5329. BaseClass::Event_Killed( info );
  5330. m_OnBreak.FireOutput( this, this );
  5331. }
  5332. //--------------------------------------------------------------------------------------------------------
  5333. int CCSPropExplodingBarrel::OnTakeDamage( const CTakeDamageInfo &info )
  5334. {
  5335. if ( m_bExploded )
  5336. return 0;
  5337. CTakeDamageInfo subInfo = info;
  5338. if ( (info.GetDamageType() & DMG_SLASH) )
  5339. return 0;
  5340. if ( (( info.GetDamageType() & DMG_CRUSH ) || ( info.GetDamageType() & DMG_CLUB )) && info.GetDamage() < 50 )
  5341. return 0;
  5342. if ( (info.GetDamageType() & DMG_BLAST) || info.GetDamage() >= 10 || (info.GetDamageType() & DMG_BLAST_SURFACE) )
  5343. {
  5344. m_bExploded = true;
  5345. // subInfo.SetDamage( m_iMaxHealth );
  5346. SetModel( EXPLODING_BARREL_MODEL_BOTTOM );
  5347. SetCollisionGroup( COLLISION_GROUP_NONE );
  5348. // create the top of the barrel
  5349. MDLHandle_t h = mdlcache->FindMDL( EXPLODING_BARREL_MODEL_TOP );
  5350. if ( h == MDLHANDLE_INVALID )
  5351. return 0;
  5352. // Must have vphysics to place as a physics prop
  5353. studiohdr_t *pStudioHdr = mdlcache->GetStudioHdr( h );
  5354. if ( !pStudioHdr )
  5355. return 0;
  5356. // Must have vphysics to place as a physics prop
  5357. if ( !mdlcache->GetVCollide( h ) )
  5358. return 0;
  5359. // Try to create entity
  5360. CPhysicsProp *pBarrelTop = dynamic_cast< CPhysicsProp * >( CreateEntityByName( "physics_prop" ) );
  5361. if ( pBarrelTop )
  5362. {
  5363. char buf[512];
  5364. // Pass in standard key values
  5365. Q_snprintf( buf, sizeof( buf ), "%.10f %.10f %.10f", GetAbsOrigin().x, GetAbsOrigin().y, GetAbsOrigin().z );
  5366. pBarrelTop->KeyValue( "origin", buf );
  5367. Q_snprintf( buf, sizeof( buf ), "%.10f %.10f %.10f", GetAbsAngles().x, GetAbsAngles().y, GetAbsAngles().z );
  5368. pBarrelTop->KeyValue( "angles", buf );
  5369. pBarrelTop->KeyValue( "model", EXPLODING_BARREL_MODEL_TOP );
  5370. Q_snprintf( buf, sizeof( buf ), "%d", 1792 );
  5371. pBarrelTop->KeyValue( "spawnflags", buf );
  5372. pBarrelTop->Precache();
  5373. DispatchSpawn( pBarrelTop );
  5374. pBarrelTop->Activate();
  5375. m_hBarrelTop = pBarrelTop;
  5376. }
  5377. Vector vecSpot = GetAbsOrigin() + Vector( RandomFloat( -4, -4 ), RandomFloat( -4, -4 ), 16 );
  5378. QAngle angThrust = QAngle( RandomFloat( -84, -98 ), 0, 0 );
  5379. // CBaseEntity *pThruster =
  5380. int nFlagsThrust = SF_THRUST_FORCE | SF_THRUST_LOCAL_ORIENTATION | SF_THRUST_MASS_INDEPENDENT;
  5381. CreatePhysThruster( vecSpot, angThrust, pBarrelTop, 15000, 0.15, true, nFlagsThrust );
  5382. // For E3, no sparks
  5383. int nFlags = SF_ENVEXPLOSION_NOSPARKS | SF_ENVEXPLOSION_NODLIGHTS | SF_ENVEXPLOSION_NOSMOKE | SF_ENVEXPLOSION_NOSOUND | SF_ENVEXPLOSION_NOFIREBALL;
  5384. ExplosionCreate( WorldSpaceCenter(), GetAbsAngles(), info.GetAttacker(), 2600, 360,
  5385. nFlags,
  5386. 0.0f, this, CLASS_NONE );
  5387. EmitSound( EXPLODING_BARREL_EXPLODE_SND1 );
  5388. EmitSound( EXPLODING_BARREL_EXPLODE_SND2 );
  5389. DispatchParticleEffect( "explosion_hegrenade_interior", vecSpot, GetAbsAngles() );
  5390. DispatchParticleEffect( "dust_burning_engine", GetAbsOrigin(), GetAbsAngles(), PATTACH_ABSORIGIN_FOLLOW, pBarrelTop );
  5391. SetThink( &CCSPropExplodingBarrel::StopParticle );
  5392. SetNextThink( gpGlobals->curtime + 2 );
  5393. }
  5394. return BaseClass::OnTakeDamage( info );
  5395. }
  5396. void CCSPropExplodingBarrel::StopParticle( void )
  5397. {
  5398. StopParticleEffects( m_hBarrelTop.Get() );
  5399. //DispatchParticleEffect( "", PATTACH_ABSORIGIN, m_hBarrelTop.Get(), 0, true );
  5400. SetThink( &CCSPropExplodingBarrel::FadeOut );
  5401. SetNextThink( gpGlobals->curtime + 4 );
  5402. SetRenderAlpha( 255 );
  5403. m_nRenderMode = kRenderNormal;
  5404. }
  5405. //-----------------------------------------------------------------------------
  5406. // Purpose: Fade out slowly
  5407. //-----------------------------------------------------------------------------
  5408. void CCSPropExplodingBarrel::FadeOut( void )
  5409. {
  5410. float dt = gpGlobals->frametime;
  5411. if ( dt > 0.1f )
  5412. dt = 0.1f;
  5413. m_hBarrelTop.Get()->m_nRenderMode = kRenderTransTexture;
  5414. int speed = MAX( 2, 256 * dt ); // fade out
  5415. m_hBarrelTop.Get()->SetRenderAlpha( UTIL_Approach( 0, m_hBarrelTop.Get()->m_clrRender->a, speed ) );
  5416. if ( m_hBarrelTop.Get()->m_clrRender->a == 0 )
  5417. {
  5418. UTIL_Remove( m_hBarrelTop.Get() );
  5419. m_hBarrelTop = NULL;
  5420. }
  5421. else
  5422. {
  5423. SetNextThink( gpGlobals->curtime );
  5424. }
  5425. }
  5426. //-----------------------------------------------------------------------------
  5427. // Purpose: Draw any debug text overlays
  5428. // Output : Current text offset from the top
  5429. //-----------------------------------------------------------------------------
  5430. int CCSPropExplodingBarrel::DrawDebugTextOverlays( void )
  5431. {
  5432. return 0;
  5433. }
  5434. BEGIN_DATADESC(CPropDoorRotatingBreakable)
  5435. DEFINE_INPUTFUNC( FIELD_VOID, "SetUnbreakable", InputSetUnbreakable ),
  5436. DEFINE_INPUTFUNC( FIELD_VOID, "SetBreakable", InputSetBreakable ),
  5437. END_DATADESC()
  5438. LINK_ENTITY_TO_CLASS(prop_door_rotating, CPropDoorRotatingBreakable);
  5439. //--------------------------------------------------------------------------------------------------------
  5440. // Scale damage force by mass to get a velocity the damage should impart to the physics object
  5441. Vector GetVelocityFromDamageForce( const CTakeDamageInfo &info, const CBaseEntity *pEntity )
  5442. {
  5443. if ( !pEntity || pEntity->VPhysicsGetNonShadowMass() <= 0.0f )
  5444. return vec3_origin;
  5445. Vector force = info.GetDamageForce();
  5446. float invMass = 1 / pEntity->VPhysicsGetNonShadowMass();
  5447. return force * invMass;
  5448. }
  5449. //--------------------------------------------------------------------------------------------------------
  5450. void CPropDoorRotatingBreakable::InputSetUnbreakable( inputdata_t &inputdata )
  5451. {
  5452. m_bBreakable = false;
  5453. if ( IsDoorClosed() )
  5454. {
  5455. BlockNav();
  5456. }
  5457. }
  5458. //--------------------------------------------------------------------------------------------------------
  5459. void CPropDoorRotatingBreakable::InputSetBreakable( inputdata_t &inputdata )
  5460. {
  5461. m_bBreakable = true;
  5462. UnblockNav();
  5463. }
  5464. //--------------------------------------------------------------------------------------------------------
  5465. bool CPropDoorRotatingBreakable::IsAbleToCloseAreaPortals( void ) const
  5466. {
  5467. return m_isAbleToCloseAreaPortals;
  5468. }
  5469. //--------------------------------------------------------------------------------------------------------
  5470. void CPropDoorRotatingBreakable::Precache( void )
  5471. {
  5472. BaseClass::Precache();
  5473. }
  5474. //--------------------------------------------------------------------------------------------------------
  5475. void CPropDoorRotatingBreakable::PrecacheBreakables( void )
  5476. {
  5477. MEM_ALLOC_CREDIT();
  5478. KeyValues *modelKeyValues = new KeyValues("");
  5479. const model_t *model = GetModel();
  5480. const char *modelName = modelinfo->GetModelName( model );
  5481. const char *modelKeyValueText = modelinfo->GetModelKeyValueText( model );
  5482. if ( modelKeyValues->LoadFromBuffer( modelName, modelKeyValueText ) )
  5483. {
  5484. KeyValues *pkvDoorOptions = modelKeyValues->FindKey("door_options");
  5485. if ( pkvDoorOptions )
  5486. {
  5487. CFmtStrN<80> str;
  5488. KeyValues *skin = pkvDoorOptions->FindKey( str.sprintf( "skin%d", m_nSkin.Get() ) );
  5489. if ( !skin )
  5490. {
  5491. skin = pkvDoorOptions->FindKey( "defaults" );
  5492. }
  5493. if ( skin )
  5494. {
  5495. int index = 1;
  5496. const char *damageState = NULL;
  5497. while ( ( damageState = skin->GetString( str.sprintf( "damage%d", index++ ), NULL ) ) != NULL )
  5498. {
  5499. str.sprintf( "models/%s.mdl", damageState );
  5500. char *modelName = str.Access();
  5501. V_FixSlashes( modelName, '/' );
  5502. PropBreakablePrecacheAll( AllocPooledString( modelName ) );
  5503. }
  5504. }
  5505. }
  5506. else
  5507. {
  5508. DevMsg( "Breakable door %s has no door_options\n", modelName );
  5509. }
  5510. }
  5511. else
  5512. {
  5513. DevMsg( "Breakable door %s has no KeyValues\n", modelName );
  5514. }
  5515. modelKeyValues->deleteThis();
  5516. }
  5517. //--------------------------------------------------------------------------------------------------------
  5518. void CPropDoorRotatingBreakable::Spawn( void )
  5519. {
  5520. MEM_ALLOC_CREDIT();
  5521. m_isAbleToCloseAreaPortals = true;
  5522. BaseClass::Spawn();
  5523. PrecacheBreakables();
  5524. m_damageStates.RemoveAll();
  5525. m_currentDamageState = -1;
  5526. m_blockedTeamNumber = TEAM_ANY;
  5527. KeyValues *modelKeyValues = new KeyValues("");
  5528. if ( modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
  5529. {
  5530. KeyValues *pkvDoorOptions = modelKeyValues->FindKey("door_options");
  5531. if ( pkvDoorOptions )
  5532. {
  5533. CFmtStrN<80> str;
  5534. KeyValues *skin = pkvDoorOptions->FindKey( str.sprintf( "skin%d", m_nSkin.Get() ) );
  5535. if ( !skin )
  5536. {
  5537. skin = pkvDoorOptions->FindKey( "defaults" );
  5538. }
  5539. if ( skin )
  5540. {
  5541. int index = 1;
  5542. const char *damageState = NULL;
  5543. while ( ( damageState = skin->GetString( str.sprintf( "damage%d", index++ ), NULL ) ) != NULL )
  5544. {
  5545. str.sprintf( "models/%s.mdl", damageState );
  5546. char *modelName = str.Access();
  5547. V_FixSlashes( modelName, '/' );
  5548. PrecacheModel( modelName );
  5549. m_damageStates.AddToTail( AllocPooledString( damageState) );
  5550. }
  5551. }
  5552. }
  5553. }
  5554. modelKeyValues->deleteThis();
  5555. m_bBreakable = HasSpawnFlags( SF_DOOR_START_BREAKABLE ) ? true : false;
  5556. m_blockedNavAreaID = 0;
  5557. // if ( IsDoorClosed() )
  5558. // {
  5559. // BlockNavArea( true );
  5560. // }
  5561. for ( int i = 0; i < MAX_NAV_TEAMS; ++i )
  5562. {
  5563. m_isBlockingNav[i] = false;
  5564. }
  5565. if ( m_bBreakable )
  5566. {
  5567. UnblockNav();
  5568. }
  5569. else if ( IsDoorClosed() && m_bLocked )
  5570. {
  5571. Lock();
  5572. }
  5573. }
  5574. //-----------------------------------------------------------------------------
  5575. // Purpose: Locks the door so that it cannot be opened.
  5576. //-----------------------------------------------------------------------------
  5577. void CPropDoorRotatingBreakable::Lock( void )
  5578. {
  5579. if ( IsDoorClosed() && m_bBreakable == false )
  5580. {
  5581. BlockNav();
  5582. }
  5583. BaseClass::Lock();
  5584. }
  5585. //-----------------------------------------------------------------------------
  5586. // Purpose: Unlocks the door so that it can be opened.
  5587. //-----------------------------------------------------------------------------
  5588. void CPropDoorRotatingBreakable::Unlock( void )
  5589. {
  5590. UnblockNav();
  5591. BaseClass::Unlock();
  5592. }
  5593. //--------------------------------------------------------------------------------------------------------
  5594. void CPropDoorRotatingBreakable::UpdateBlocked( bool bBlocked )
  5595. {
  5596. NavAreaCollector collector( true );
  5597. Extent extent;
  5598. extent.Init( this );
  5599. TheNavMesh->ForAllAreasOverlappingExtent( collector, extent );
  5600. for ( int i = 0; i < collector.m_area.Count(); ++i )
  5601. {
  5602. CNavArea *area = collector.m_area[i];
  5603. if ( bBlocked )
  5604. area->MarkAsBlocked( TEAM_ANY, this, false );
  5605. else
  5606. area->MarkAsUnblocked( TEAM_ANY, false );
  5607. }
  5608. }
  5609. //--------------------------------------------------------------------------------------------------------
  5610. // Forces nav areas to unblock when the nav blocker is deleted (round restart) so flow can compute properly
  5611. void CPropDoorRotatingBreakable::UpdateOnRemove( void )
  5612. {
  5613. UnblockNav();
  5614. //gm_NavBlockers.FindAndRemove( this );
  5615. BaseClass::UpdateOnRemove();
  5616. }
  5617. //--------------------------------------------------------------------------------------------------------
  5618. void CPropDoorRotatingBreakable::BlockNav( void )
  5619. {
  5620. if ( m_blockedTeamNumber == TEAM_ANY )
  5621. {
  5622. for ( int i = 0; i < MAX_NAV_TEAMS; ++i )
  5623. {
  5624. m_isBlockingNav[i] = true;
  5625. }
  5626. }
  5627. else
  5628. {
  5629. int teamNumber = m_blockedTeamNumber % MAX_NAV_TEAMS;
  5630. m_isBlockingNav[teamNumber] = true;
  5631. }
  5632. UpdateBlocked( true );
  5633. }
  5634. //--------------------------------------------------------------------------------------------------------
  5635. void CPropDoorRotatingBreakable::UnblockNav( void )
  5636. {
  5637. if ( m_blockedTeamNumber == TEAM_ANY )
  5638. {
  5639. for ( int i = 0; i < MAX_NAV_TEAMS; ++i )
  5640. {
  5641. m_isBlockingNav[i] = false;
  5642. }
  5643. }
  5644. else
  5645. {
  5646. int teamNumber = m_blockedTeamNumber % MAX_NAV_TEAMS;
  5647. m_isBlockingNav[teamNumber] = false;
  5648. }
  5649. UpdateBlocked( false );
  5650. }
  5651. //--------------------------------------------------------------------------------------------------------
  5652. // functor that blocks areas in our extent
  5653. bool CPropDoorRotatingBreakable::operator()( CNavArea *area )
  5654. {
  5655. area->MarkAsBlocked( m_blockedTeamNumber, this );
  5656. return true;
  5657. }
  5658. //--------------------------------------------------------------------------------------------------------
  5659. // bool CPropDoorRotatingBreakable::CalculateBlocked( bool *pResultByTeam, const Vector &vecMins, const Vector &vecMaxs )
  5660. // {
  5661. // int nTeamsBlocked = 0;
  5662. // int i;
  5663. // bool bBlocked = false;
  5664. // for ( i = 0; i < MAX_NAV_TEAMS; ++i )
  5665. // {
  5666. // pResultByTeam[i] = false;
  5667. // }
  5668. //
  5669. // bool bIsIntersecting = false;
  5670. //
  5671. // for ( i = 0; i < MAX_NAV_TEAMS; ++i )
  5672. // {
  5673. // if ( !pResultByTeam[i] )
  5674. // {
  5675. // //const CCollisionProperty *pCollide = CollisionProp();
  5676. // //BoxAngles( pCollide->GetCollisionOrigin(), pCollide->OBBMins(), pCollide->OBBMaxs(), pCollide->GetCollisionAngles(), r, g, b, a, flDuration );
  5677. //
  5678. // Vector vecMinsDoor;
  5679. // Vector vecMaxsDoor;
  5680. // VPhysicsGetObject()->GetCollide()->CollisionProp()->WorldSpaceAABB( &vecMinsDoor, &vecMaxsDoor );
  5681. //
  5682. // if ( IsBoxIntersectingBox( CollisionProp()->OBBMins(), CollisionProp()->OBBMaxs(), vecMins, vecMaxs ) )
  5683. // {
  5684. // bBlocked = true;
  5685. // pResultByTeam[i] = true;
  5686. // nTeamsBlocked++;
  5687. // }
  5688. // else
  5689. // {
  5690. // continue;
  5691. // }
  5692. // }
  5693. // }
  5694. //
  5695. // return bBlocked;
  5696. // }
  5697. //--------------------------------------------------------------------------------------------------------
  5698. void CPropDoorRotatingBreakable::Event_Killed( const CTakeDamageInfo &info )
  5699. {
  5700. if ( m_damageStates.Count() )
  5701. {
  5702. int targetDamageState = m_damageStates.Count() - 1;
  5703. if ( targetDamageState > m_currentDamageState )
  5704. {
  5705. PhysBreakSound( this, VPhysicsGetObject(), GetAbsOrigin() );
  5706. CPASFilter filter( WorldSpaceCenter() );
  5707. while ( targetDamageState > m_currentDamageState )
  5708. {
  5709. Vector offset( info.GetDamageForce() );
  5710. float forceLen = offset.NormalizeInPlace();
  5711. Vector force( offset );
  5712. offset *= 10.0f; // offset some so fragments aren't spawned stuck in the door
  5713. force *= MIN( forceLen, 300.0f ); // cap the damage force so pieces don't go spinning off
  5714. color24 color = GetRenderColor();
  5715. te->PhysicsProp( filter, -1, GetModelIndex(), m_nSkin, GetAbsOrigin() + offset, GetAbsAngles(), force, 1, GetEffects(), color );
  5716. ++m_currentDamageState;
  5717. CFmtStrN<80> str;
  5718. str.sprintf( "models/%s.mdl", STRING( m_damageStates[m_currentDamageState] ) );
  5719. char *modelName = str.Access();
  5720. V_FixSlashes( modelName, '/' );
  5721. SetModel( STRING( AllocPooledString( modelName ) ) );
  5722. }
  5723. }
  5724. }
  5725. if ( IsDoorClosed() )
  5726. {
  5727. UpdateAreaPortals( true );
  5728. }
  5729. // do dialogue
  5730. // {
  5731. // AI_CriteriaSet contexts;
  5732. // CBaseEntity *attacker = info.GetAttacker();
  5733. // if (attacker)
  5734. // {
  5735. // CTeam *team = attacker->GetTeam();
  5736. // if (team)
  5737. // {
  5738. // contexts.AppendCriteria("brokenby",team->GetName());
  5739. // }
  5740. // }
  5741. // g_ResponseQueueManager.GetQueue()->Add( "DoorBroken", &contexts, 0.0f, kDRT_ANY, this );
  5742. // }
  5743. m_OnBreak.FireOutput( this, this );
  5744. BaseClass::Event_Killed( info );
  5745. }
  5746. //--------------------------------------------------------------------------------------------------------
  5747. int CPropDoorRotatingBreakable::OnTakeDamage( const CTakeDamageInfo &info )
  5748. {
  5749. MEM_ALLOC_CREDIT();
  5750. int oldHealth = m_iHealth;
  5751. CTakeDamageInfo subInfo = info;
  5752. const float DoorExplosionBreakDamage = 40.0f;
  5753. if ( (info.GetDamageType() & DMG_BLAST) && (info.GetDamage() >= DoorExplosionBreakDamage || (info.GetDamageType() & DMG_BLAST_SURFACE)) )
  5754. {
  5755. subInfo.SetDamage( m_iMaxHealth );
  5756. }
  5757. if ( !m_bBreakable )
  5758. return 0;
  5759. int ret = BaseClass::OnTakeDamage( subInfo );
  5760. int newHealth = m_iHealth;
  5761. if ( oldHealth != newHealth && newHealth > 0 && m_damageStates.Count() > 0 )
  5762. {
  5763. // We were hurt, but are still alive. Check to see if we should change damage states.
  5764. int healthPerDamageState = m_iMaxHealth / (m_damageStates.Count()+1);
  5765. int targetDamageState = -1;
  5766. while ( newHealth < m_iMaxHealth - healthPerDamageState * (targetDamageState + 2) )
  5767. {
  5768. ++targetDamageState;
  5769. }
  5770. targetDamageState = clamp( targetDamageState, -1, m_damageStates.Count() - 1 );
  5771. if ( targetDamageState > m_currentDamageState )
  5772. {
  5773. m_isAbleToCloseAreaPortals = false;
  5774. if ( IsDoorClosed() )
  5775. {
  5776. UpdateAreaPortals( true );
  5777. }
  5778. Activity mainActivity = GetSequenceActivity( GetSequence() );
  5779. float mainCycle = GetCycle();
  5780. PhysBreakSound( this, VPhysicsGetObject(), GetAbsOrigin() );
  5781. CPASFilter filter( WorldSpaceCenter() );
  5782. while ( targetDamageState > m_currentDamageState )
  5783. {
  5784. // remember our physics material index, so we can reapply it after our damage model changes.
  5785. int nPhysMaterial = -1;
  5786. if ( VPhysicsGetObject() )
  5787. nPhysMaterial = VPhysicsGetObject()->GetMaterialIndex();
  5788. // NGH: Transfer the velocity of the projectile into the chunk
  5789. Vector addedVelocity = GetVelocityFromDamageForce( info, this );
  5790. color24 color = GetRenderColor();
  5791. te->PhysicsProp( filter, -1, GetModelIndex(), m_nSkin, GetAbsOrigin(), GetAbsAngles(), addedVelocity, 1, GetEffects(), color );
  5792. ++m_currentDamageState;
  5793. CFmtStrN<80> str;
  5794. str.sprintf( "models/%s.mdl", STRING( m_damageStates[m_currentDamageState] ) );
  5795. char *modelName = str.Access();
  5796. V_FixSlashes( modelName, '/' );
  5797. SetModel( STRING( AllocPooledString( modelName ) ) );
  5798. VPhysicsDestroyObject();
  5799. VPhysicsInitShadow( false, false );
  5800. KeyValues *modelKeyValues = new KeyValues("");
  5801. if ( !modelKeyValues->LoadFromBuffer( modelinfo->GetModelName( GetModel() ), modelinfo->GetModelKeyValueText( GetModel() ) ) )
  5802. {
  5803. modelKeyValues->deleteThis();
  5804. }
  5805. else
  5806. {
  5807. // Do we have a props section?
  5808. KeyValues *pkvPropData = modelKeyValues->FindKey("prop_data");
  5809. if ( pkvPropData )
  5810. {
  5811. SetDmgModBullet( pkvPropData->GetFloat( "dmg.bullets", GetDmgModBullet() ) );
  5812. SetDmgModClub( pkvPropData->GetFloat( "dmg.club", GetDmgModClub() ) );
  5813. SetDmgModExplosive( pkvPropData->GetFloat( "dmg.explosive", GetDmgModExplosive() ) );
  5814. SetDmgModFire( pkvPropData->GetFloat( "dmg.fire", GetDmgModFire() ) );
  5815. SetBlocksLOS( pkvPropData->GetBool( "blocklos", BlocksLOS() ) );
  5816. // If this damage state is marked as being debris, switch our collision group.
  5817. if ( pkvPropData->GetBool( "isdebris", false ) )
  5818. {
  5819. SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  5820. }
  5821. }
  5822. modelKeyValues->deleteThis();
  5823. }
  5824. // reapply the physics damage material
  5825. if ( VPhysicsGetObject() && nPhysMaterial != -1 )
  5826. VPhysicsGetObject()->SetMaterialIndex( nPhysMaterial );
  5827. }
  5828. int mainSequence = SelectWeightedSequence(mainActivity);
  5829. if ( mainSequence >= 0 )
  5830. {
  5831. SetSequence( mainSequence );
  5832. SetCycle( mainCycle );
  5833. }
  5834. m_OnBreak.FireOutput( this, this );
  5835. }
  5836. }
  5837. return ret;
  5838. }
  5839. //-----------------------------------------------------------------------------
  5840. // Purpose: Draw any debug text overlays
  5841. // Output : Current text offset from the top
  5842. //-----------------------------------------------------------------------------
  5843. int CPropDoorRotatingBreakable::DrawDebugTextOverlays( void )
  5844. {
  5845. int text_offset = BaseClass::DrawDebugTextOverlays();
  5846. if (m_debugOverlays & OVERLAY_TEXT_BIT)
  5847. {
  5848. char tempstr[512];
  5849. if ( m_bBreakable )
  5850. {
  5851. Q_strncpy( tempstr, "DOOR IS BREAKABLE", sizeof(tempstr) );
  5852. }
  5853. else
  5854. {
  5855. Q_strncpy( tempstr, "DOOR IS NOT BREAKABLE", sizeof(tempstr) );
  5856. }
  5857. EntityText( text_offset, tempstr, 0 );
  5858. text_offset++;
  5859. CFmtStr str;
  5860. // FIRST_GAME_TEAM skips TEAM_SPECTATOR and TEAM_UNASSIGNED, so we can print
  5861. // useful team names in a non-game-specific fashion.
  5862. for ( int i = FIRST_GAME_TEAM; i < FIRST_GAME_TEAM + MAX_NAV_TEAMS; ++i )
  5863. {
  5864. if ( m_isBlockingNav[i] == true )
  5865. {
  5866. EntityText( text_offset++, str.sprintf( "blocking team %d", i ), 0 );
  5867. }
  5868. }
  5869. NavAreaCollector collector( true );
  5870. Extent extent;
  5871. extent.Init( this );
  5872. TheNavMesh->ForAllAreasOverlappingExtent( collector, extent );
  5873. for ( int i = 0; i < collector.m_area.Count(); ++i )
  5874. {
  5875. CNavArea *area = collector.m_area[i];
  5876. Extent areaExtent;
  5877. area->GetExtent( &areaExtent );
  5878. debugoverlay->AddBoxOverlay( vec3_origin, areaExtent.lo, areaExtent.hi, vec3_angle, 0, 255, 0, 10, NDEBUG_PERSIST_TILL_NEXT_SERVER );
  5879. }
  5880. }
  5881. return text_offset;
  5882. }
  5883. LINK_ENTITY_TO_CLASS( prop_sphere, CPhysSphere );
  5884. // ------------------------------------------------------------------------------------------ //
  5885. // Special version of func_physbox.
  5886. // ------------------------------------------------------------------------------------------ //
  5887. class CPhysBoxMultiplayer : public CPhysBox, public IMultiplayerPhysics
  5888. {
  5889. public:
  5890. DECLARE_CLASS( CPhysBoxMultiplayer, CPhysBox );
  5891. virtual int GetMultiplayerPhysicsMode()
  5892. {
  5893. return m_iPhysicsMode;
  5894. }
  5895. virtual float GetMass()
  5896. {
  5897. return m_fMass;
  5898. }
  5899. virtual bool IsAsleep()
  5900. {
  5901. return VPhysicsGetObject()->IsAsleep();
  5902. }
  5903. CNetworkVar( int, m_iPhysicsMode ); // One of the PHYSICS_MULTIPLAYER_ defines.
  5904. CNetworkVar( float, m_fMass );
  5905. DECLARE_DATADESC();
  5906. DECLARE_SERVERCLASS();
  5907. virtual void Activate()
  5908. {
  5909. BaseClass::Activate();
  5910. SetCollisionGroup( COLLISION_GROUP_PUSHAWAY );
  5911. m_fMass = VPhysicsGetObject()->GetMass();
  5912. }
  5913. };
  5914. LINK_ENTITY_TO_CLASS( func_physbox_multiplayer, CPhysBoxMultiplayer );
  5915. BEGIN_DATADESC( CPhysBoxMultiplayer )
  5916. END_DATADESC()
  5917. IMPLEMENT_SERVERCLASS_ST( CPhysBoxMultiplayer, DT_PhysBoxMultiplayer )
  5918. SendPropInt( SENDINFO( m_iPhysicsMode ), 1, SPROP_UNSIGNED ),
  5919. SendPropFloat( SENDINFO( m_fMass ), 0, SPROP_NOSCALE ),
  5920. END_SEND_TABLE()
  5921. class CPhysicsPropMultiplayer : public CPhysicsProp, public IMultiplayerPhysics
  5922. {
  5923. DECLARE_CLASS( CPhysicsPropMultiplayer, CPhysicsProp );
  5924. CNetworkVar( int, m_iPhysicsMode ); // One of the PHYSICS_MULTIPLAYER_ defines.
  5925. CNetworkVar( float, m_fMass );
  5926. DECLARE_SERVERCLASS();
  5927. DECLARE_DATADESC();
  5928. CPhysicsPropMultiplayer()
  5929. {
  5930. m_iPhysicsMode = PHYSICS_MULTIPLAYER_AUTODETECT;
  5931. m_usingCustomCollisionBounds = false;
  5932. }
  5933. // IBreakableWithPropData:
  5934. void SetPhysicsMode(int iMode)
  5935. {
  5936. m_iPhysicsMode = iMode;
  5937. }
  5938. int GetPhysicsMode() { return m_iPhysicsMode; }
  5939. // IMultiplayerPhysics:
  5940. int GetMultiplayerPhysicsMode() { return m_iPhysicsMode; }
  5941. float GetMass() { return m_fMass; }
  5942. bool IsAsleep() { return !m_bAwake; }
  5943. bool IsDebris( void ) { return ( ( m_spawnflags & SF_PHYSPROP_DEBRIS ) != 0 ); }
  5944. virtual void VPhysicsUpdate( IPhysicsObject *pPhysics )
  5945. {
  5946. BaseClass::VPhysicsUpdate( pPhysics );
  5947. if ( sv_turbophysics.GetBool() )
  5948. {
  5949. // If the object is set to debris, don't let turbo physics change it.
  5950. if ( IsDebris() )
  5951. return;
  5952. if ( m_bAwake )
  5953. {
  5954. SetCollisionGroup( COLLISION_GROUP_PUSHAWAY );
  5955. }
  5956. else if ( m_iPhysicsMode == PHYSICS_MULTIPLAYER_NON_SOLID )
  5957. {
  5958. SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  5959. }
  5960. else
  5961. {
  5962. SetCollisionGroup( COLLISION_GROUP_NONE );
  5963. }
  5964. }
  5965. }
  5966. virtual void Spawn( void )
  5967. {
  5968. BaseClass::Spawn();
  5969. // if no physicsmode was defined by .QC or propdata.txt,
  5970. // use auto detect based on size & mass
  5971. if ( m_iPhysicsMode == PHYSICS_MULTIPLAYER_AUTODETECT )
  5972. {
  5973. if ( VPhysicsGetObject() )
  5974. {
  5975. m_iPhysicsMode = GetAutoMultiplayerPhysicsMode(
  5976. CollisionProp()->OBBSize(), VPhysicsGetObject()->GetMass() );
  5977. }
  5978. else
  5979. {
  5980. UTIL_Remove( this );
  5981. return;
  5982. }
  5983. }
  5984. // check if map maker overrides physics mode to force a server-side entity
  5985. if ( GetSpawnFlags() & SF_PHYSPROP_FORCE_SERVER_SIDE )
  5986. {
  5987. SetPhysicsMode( PHYSICS_MULTIPLAYER_NON_SOLID );
  5988. }
  5989. if ( m_iPhysicsMode == PHYSICS_MULTIPLAYER_CLIENTSIDE )
  5990. {
  5991. if ( engine->IsInEditMode() )
  5992. {
  5993. // in map edit mode always spawn as server phys prop
  5994. SetPhysicsMode( PHYSICS_MULTIPLAYER_NON_SOLID );
  5995. }
  5996. else
  5997. {
  5998. // don't spawn clientside props on server
  5999. UTIL_Remove( this );
  6000. return;
  6001. }
  6002. }
  6003. if ( GetCollisionGroup() == COLLISION_GROUP_NONE )
  6004. SetCollisionGroup( COLLISION_GROUP_PUSHAWAY );
  6005. // Items marked as debris should be set as such.
  6006. if ( IsDebris() )
  6007. {
  6008. SetCollisionGroup( COLLISION_GROUP_DEBRIS );
  6009. }
  6010. m_fMass = VPhysicsGetObject()->GetMass();
  6011. // VPhysicsGetObject() is NULL on the client, which prevents the client from finding a decent
  6012. // AABB surrounding the collision bounds. If we've got a VPhysicsGetObject()->GetCollide(), we'll
  6013. // grab it's unrotated bounds and use it to calculate our collision surrounding bounds. This
  6014. // can end up larger than the CollisionProp() would have calculated on its own, but it'll be
  6015. // identical on the client and the server.
  6016. m_usingCustomCollisionBounds = false;
  6017. if ( ( GetSolid() == SOLID_VPHYSICS ) && ( GetMoveType() == MOVETYPE_VPHYSICS ) )
  6018. {
  6019. IPhysicsObject *pPhysics = VPhysicsGetObject();
  6020. if ( pPhysics && pPhysics->GetCollide() )
  6021. {
  6022. physcollision->CollideGetAABB( &m_collisionMins.GetForModify(), &m_collisionMaxs.GetForModify(), pPhysics->GetCollide(), vec3_origin, vec3_angle );
  6023. CollisionProp()->SetSurroundingBoundsType( USE_GAME_CODE );
  6024. m_usingCustomCollisionBounds = true;
  6025. }
  6026. }
  6027. }
  6028. virtual void ComputeWorldSpaceSurroundingBox( Vector *mins, Vector *maxs )
  6029. {
  6030. Assert( m_usingCustomCollisionBounds );
  6031. Assert( mins != NULL && maxs != NULL );
  6032. if ( !mins || !maxs )
  6033. return;
  6034. // Take our saved collision bounds, and transform into world space
  6035. TransformAABB( EntityToWorldTransform(), m_collisionMins, m_collisionMaxs, *mins, *maxs );
  6036. }
  6037. private:
  6038. bool m_usingCustomCollisionBounds;
  6039. CNetworkVector( m_collisionMins );
  6040. CNetworkVector( m_collisionMaxs );
  6041. };
  6042. LINK_ENTITY_TO_CLASS( prop_physics_multiplayer, CPhysicsPropMultiplayer );
  6043. BEGIN_DATADESC( CPhysicsPropMultiplayer )
  6044. DEFINE_KEYFIELD( m_iPhysicsMode, FIELD_INTEGER, "physicsmode" ),
  6045. DEFINE_FIELD( m_fMass, FIELD_FLOAT ),
  6046. DEFINE_FIELD( m_usingCustomCollisionBounds, FIELD_BOOLEAN ),
  6047. DEFINE_FIELD( m_collisionMins, FIELD_VECTOR ),
  6048. DEFINE_FIELD( m_collisionMaxs, FIELD_VECTOR ),
  6049. END_DATADESC()
  6050. IMPLEMENT_SERVERCLASS_ST( CPhysicsPropMultiplayer, DT_PhysicsPropMultiplayer )
  6051. SendPropInt( SENDINFO( m_iPhysicsMode ), 2, SPROP_UNSIGNED ),
  6052. SendPropFloat( SENDINFO( m_fMass ), 0, SPROP_NOSCALE ),
  6053. SendPropVector( SENDINFO( m_collisionMins ), 0, SPROP_NOSCALE ),
  6054. SendPropVector( SENDINFO( m_collisionMaxs ), 0, SPROP_NOSCALE ),
  6055. END_SEND_TABLE()
  6056. #define RESPAWNABLE_PROP_DEFAULT_TIME 60.0f
  6057. class CPhysicsPropRespawnable : public CPhysicsProp
  6058. {
  6059. DECLARE_CLASS( CPhysicsPropRespawnable, CPhysicsProp );
  6060. DECLARE_DATADESC();
  6061. public:
  6062. CPhysicsPropRespawnable();
  6063. virtual void Spawn( void );
  6064. virtual void Event_Killed( const CTakeDamageInfo &info );
  6065. void Materialize( void );
  6066. private:
  6067. Vector m_vOriginalSpawnOrigin;
  6068. QAngle m_vOriginalSpawnAngles;
  6069. Vector m_vOriginalMins;
  6070. Vector m_vOriginalMaxs;
  6071. float m_flRespawnTime;
  6072. };
  6073. LINK_ENTITY_TO_CLASS( prop_physics_respawnable, CPhysicsPropRespawnable );
  6074. BEGIN_DATADESC( CPhysicsPropRespawnable )
  6075. DEFINE_THINKFUNC( Materialize ),
  6076. DEFINE_KEYFIELD( m_flRespawnTime, FIELD_FLOAT, "RespawnTime" ),
  6077. DEFINE_FIELD( m_vOriginalSpawnOrigin, FIELD_POSITION_VECTOR ),
  6078. DEFINE_FIELD( m_vOriginalSpawnAngles, FIELD_VECTOR ),
  6079. DEFINE_FIELD( m_vOriginalMins, FIELD_VECTOR ),
  6080. DEFINE_FIELD( m_vOriginalMaxs, FIELD_VECTOR ),
  6081. END_DATADESC()
  6082. CPhysicsPropRespawnable::CPhysicsPropRespawnable( void )
  6083. {
  6084. m_flRespawnTime = 0.0f;
  6085. }
  6086. void CPhysicsPropRespawnable::Spawn( void )
  6087. {
  6088. BaseClass::Spawn();
  6089. m_vOriginalSpawnOrigin = GetAbsOrigin();
  6090. m_vOriginalSpawnAngles = GetAbsAngles();
  6091. m_vOriginalMins = CollisionProp()->OBBMins();
  6092. m_vOriginalMaxs = CollisionProp()->OBBMaxs();
  6093. if ( m_flRespawnTime == 0.0f )
  6094. {
  6095. m_flRespawnTime = RESPAWNABLE_PROP_DEFAULT_TIME;
  6096. }
  6097. SetOwnerEntity( NULL );
  6098. }
  6099. void CPhysicsPropRespawnable::Event_Killed( const CTakeDamageInfo &info )
  6100. {
  6101. IPhysicsObject *pPhysics = VPhysicsGetObject();
  6102. if ( pPhysics && !pPhysics->IsMoveable() )
  6103. {
  6104. pPhysics->EnableMotion( true );
  6105. VPhysicsTakeDamage( info );
  6106. }
  6107. Break( info.GetInflictor(), info );
  6108. PhysCleanupFrictionSounds( this );
  6109. VPhysicsDestroyObject();
  6110. CBaseEntity::PhysicsRemoveTouchedList( this );
  6111. CBaseEntity::PhysicsRemoveGroundList( this );
  6112. DestroyAllDataObjects();
  6113. AddEffects( EF_NODRAW );
  6114. if ( IsOnFire() || IsDissolving() )
  6115. {
  6116. UTIL_Remove( GetEffectEntity() );
  6117. }
  6118. Teleport( &m_vOriginalSpawnOrigin, &m_vOriginalSpawnAngles, NULL );
  6119. SetContextThink( NULL, 0, "PROP_CLEARFLAGS" );
  6120. SetThink( &CPhysicsPropRespawnable::Materialize );
  6121. SetNextThink( gpGlobals->curtime + m_flRespawnTime );
  6122. }
  6123. void CPhysicsPropRespawnable::Materialize( void )
  6124. {
  6125. trace_t tr;
  6126. UTIL_TraceHull( m_vOriginalSpawnOrigin, m_vOriginalSpawnOrigin, m_vOriginalMins, m_vOriginalMaxs, MASK_SOLID, this, COLLISION_GROUP_NONE, &tr );
  6127. if ( tr.startsolid || tr.allsolid )
  6128. {
  6129. //Try again in a second.
  6130. SetNextThink( gpGlobals->curtime + 1.0f );
  6131. return;
  6132. }
  6133. RemoveEffects( EF_NODRAW );
  6134. Spawn();
  6135. }
  6136. //------------------------------------------------------------------------------
  6137. // Purpose: Create a prop of the given type
  6138. //------------------------------------------------------------------------------
  6139. void CC_Prop_Dynamic_Create( const CCommand &args )
  6140. {
  6141. if ( args.ArgC() != 2 )
  6142. return;
  6143. // Figure out where to place it
  6144. CBasePlayer* pPlayer = UTIL_GetCommandClient();
  6145. Vector forward;
  6146. pPlayer->EyeVectors( &forward );
  6147. trace_t tr;
  6148. UTIL_TraceLine( pPlayer->EyePosition(),
  6149. pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH, MASK_NPCSOLID,
  6150. pPlayer, COLLISION_GROUP_NONE, &tr );
  6151. // No hit? We're done.
  6152. if ( tr.fraction == 1.0 )
  6153. return;
  6154. MDLCACHE_CRITICAL_SECTION();
  6155. char pModelName[512];
  6156. Q_snprintf( pModelName, sizeof(pModelName), "models/%s", args[1] );
  6157. Q_DefaultExtension( pModelName, ".mdl", sizeof(pModelName) );
  6158. MDLHandle_t h = mdlcache->FindMDL( pModelName );
  6159. if ( h == MDLHANDLE_INVALID )
  6160. return;
  6161. bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed();
  6162. CBaseEntity::SetAllowPrecache( true );
  6163. vcollide_t *pVCollide = mdlcache->GetVCollide( h );
  6164. Vector xaxis( 1.0f, 0.0f, 0.0f );
  6165. Vector yaxis;
  6166. CrossProduct( tr.plane.normal, xaxis, yaxis );
  6167. if ( VectorNormalize( yaxis ) < 1e-3 )
  6168. {
  6169. xaxis.Init( 0.0f, 0.0f, 1.0f );
  6170. CrossProduct( tr.plane.normal, xaxis, yaxis );
  6171. VectorNormalize( yaxis );
  6172. }
  6173. CrossProduct( yaxis, tr.plane.normal, xaxis );
  6174. VectorNormalize( xaxis );
  6175. VMatrix entToWorld;
  6176. entToWorld.SetBasisVectors( xaxis, yaxis, tr.plane.normal );
  6177. QAngle angles;
  6178. MatrixToAngles( entToWorld, angles );
  6179. // Try to create entity
  6180. CDynamicProp *pProp = dynamic_cast< CDynamicProp * >( CreateEntityByName( "dynamic_prop" ) );
  6181. if ( pProp )
  6182. {
  6183. char buf[512];
  6184. // Pass in standard key values
  6185. Q_snprintf( buf, sizeof(buf), "%.10f %.10f %.10f", tr.endpos.x, tr.endpos.y, tr.endpos.z );
  6186. pProp->KeyValue( "origin", buf );
  6187. Q_snprintf( buf, sizeof(buf), "%.10f %.10f %.10f", angles.x, angles.y, angles.z );
  6188. pProp->KeyValue( "angles", buf );
  6189. pProp->KeyValue( "model", pModelName );
  6190. pProp->KeyValue( "solid", pVCollide ? "6" : "2" );
  6191. pProp->KeyValue( "fademindist", "-1" );
  6192. pProp->KeyValue( "fademaxdist", "0" );
  6193. pProp->KeyValue( "fadescale", "1" );
  6194. pProp->KeyValue( "MinAnimTime", "5" );
  6195. pProp->KeyValue( "MaxAnimTime", "10" );
  6196. pProp->Precache();
  6197. DispatchSpawn( pProp );
  6198. pProp->Activate();
  6199. }
  6200. CBaseEntity::SetAllowPrecache( bAllowPrecache );
  6201. }
  6202. static ConCommand prop_dynamic_create("prop_dynamic_create", CC_Prop_Dynamic_Create, "Creates a dynamic prop with a specific .mdl aimed away from where the player is looking.\n\tArguments: {.mdl name}", FCVAR_CHEAT);
  6203. //------------------------------------------------------------------------------
  6204. // Purpose: Create a prop of the given type
  6205. //------------------------------------------------------------------------------
  6206. void CC_Prop_Physics_Create( const CCommand &args )
  6207. {
  6208. if ( args.ArgC() != 2 )
  6209. return;
  6210. char pModelName[512];
  6211. Q_snprintf( pModelName, sizeof(pModelName), "models/%s", args[1] );
  6212. Q_DefaultExtension( pModelName, ".mdl", sizeof(pModelName) );
  6213. // Figure out where to place it
  6214. CBasePlayer* pPlayer = UTIL_GetCommandClient();
  6215. Vector forward;
  6216. pPlayer->EyeVectors( &forward );
  6217. CreatePhysicsProp( pModelName, pPlayer->EyePosition(), pPlayer->EyePosition() + forward * MAX_TRACE_LENGTH, pPlayer, true );
  6218. }
  6219. static ConCommand prop_physics_create("prop_physics_create", CC_Prop_Physics_Create, "Creates a physics prop with a specific .mdl aimed away from where the player is looking.\n\tArguments: {.mdl name}", FCVAR_CHEAT);
  6220. CPhysicsProp* CreatePhysicsProp( const char *pModelName, const Vector &vTraceStart, const Vector &vTraceEnd, const IHandleEntity *pTraceIgnore, bool bRequireVCollide, const char *pClassName )
  6221. {
  6222. MDLCACHE_CRITICAL_SECTION();
  6223. MDLHandle_t h = mdlcache->FindMDL( pModelName );
  6224. if ( h == MDLHANDLE_INVALID )
  6225. return NULL;
  6226. // Must have vphysics to place as a physics prop
  6227. studiohdr_t *pStudioHdr = mdlcache->GetStudioHdr( h );
  6228. if ( !pStudioHdr )
  6229. return NULL;
  6230. // Must have vphysics to place as a physics prop
  6231. if ( bRequireVCollide && !mdlcache->GetVCollide( h ) )
  6232. return NULL;
  6233. QAngle angles( 0.0f, 0.0f, 0.0f );
  6234. Vector vecSweepMins = pStudioHdr->hull_min;
  6235. Vector vecSweepMaxs = pStudioHdr->hull_max;
  6236. trace_t tr;
  6237. UTIL_TraceHull( vTraceStart, vTraceEnd,
  6238. vecSweepMins, vecSweepMaxs, MASK_NPCSOLID, pTraceIgnore, COLLISION_GROUP_NONE, &tr );
  6239. // No hit? We're done.
  6240. if ( (tr.fraction == 1.0 && (vTraceEnd-vTraceStart).Length() > 0.01) || tr.allsolid )
  6241. return NULL;
  6242. VectorMA( tr.endpos, 1.0f, tr.plane.normal, tr.endpos );
  6243. bool bAllowPrecache = CBaseEntity::IsPrecacheAllowed();
  6244. CBaseEntity::SetAllowPrecache( true );
  6245. // Try to create entity
  6246. CPhysicsProp *pProp = dynamic_cast< CPhysicsProp * >( CreateEntityByName( pClassName ) );
  6247. if ( pProp )
  6248. {
  6249. char buf[512];
  6250. // Pass in standard key values
  6251. Q_snprintf( buf, sizeof(buf), "%.10f %.10f %.10f", tr.endpos.x, tr.endpos.y, tr.endpos.z );
  6252. pProp->KeyValue( "origin", buf );
  6253. Q_snprintf( buf, sizeof(buf), "%.10f %.10f %.10f", angles.x, angles.y, angles.z );
  6254. pProp->KeyValue( "angles", buf );
  6255. pProp->KeyValue( "model", pModelName );
  6256. pProp->KeyValue( "fademindist", "-1" );
  6257. pProp->KeyValue( "fademaxdist", "0" );
  6258. pProp->KeyValue( "fadescale", "1" );
  6259. pProp->KeyValue( "inertiaScale", "1.0" );
  6260. pProp->KeyValue( "physdamagescale", "0.1" );
  6261. pProp->Precache();
  6262. DispatchSpawn( pProp );
  6263. pProp->Activate();
  6264. }
  6265. CBaseEntity::SetAllowPrecache( bAllowPrecache );
  6266. return pProp;
  6267. }
  6268. //------------------------------------------------------------------------------
  6269. // Rotates an entity
  6270. //------------------------------------------------------------------------------
  6271. void CC_Ent_Rotate( const CCommand &args )
  6272. {
  6273. CBasePlayer* pPlayer = UTIL_GetCommandClient();
  6274. if ( !pPlayer )
  6275. return;
  6276. CBaseEntity* pEntity = pPlayer->FindPickerEntity();
  6277. if ( !pEntity )
  6278. return;
  6279. QAngle angles = pEntity->GetLocalAngles();
  6280. float flAngle = (args.ArgC() == 2) ? atof( args[1] ) : 7.5f;
  6281. VMatrix entToWorld, rot, newEntToWorld;
  6282. MatrixBuildRotateZ( rot, flAngle );
  6283. MatrixFromAngles( angles, entToWorld );
  6284. MatrixMultiply( entToWorld, rot, newEntToWorld );
  6285. MatrixToAngles( newEntToWorld, angles );
  6286. pEntity->SetLocalAngles( angles );
  6287. }
  6288. static ConCommand ent_rotate("ent_rotate", CC_Ent_Rotate, "Rotates an entity by a specified # of degrees", FCVAR_CHEAT);
  6289. // This is a dummy. The entity is entirely clientside.
  6290. LINK_ENTITY_TO_CLASS( func_proprrespawnzone, CBaseEntity );
  6291. #ifdef PORTAL2
  6292. bool UTIL_PropIsMotionDisabled( CBaseEntity *pObject )
  6293. {
  6294. CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(pObject);
  6295. if ( pProp == NULL )
  6296. return false;
  6297. return ( pProp->HasSpawnFlags( SF_PHYSPROP_MOTIONDISABLED ) );
  6298. }
  6299. void UTIL_SetPropMotionDisabled( CBaseEntity *pObject )
  6300. {
  6301. CPhysicsProp *pProp = dynamic_cast<CPhysicsProp *>(pObject);
  6302. if ( pProp == NULL )
  6303. return;
  6304. pProp->AddSpawnFlags( SF_PHYSPROP_MOTIONDISABLED );
  6305. }
  6306. #endif // PORTAL2