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.

2088 lines
69 KiB

  1. //===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #include "cbase.h"
  8. #include "portal_base2d.h"
  9. #include "portal_player.h"
  10. #include "portal/weapon_physcannon.h"
  11. #include "physics_npc_solver.h"
  12. #include "envmicrophone.h"
  13. #include "env_speaker.h"
  14. #include "func_portal_detector.h"
  15. #include "model_types.h"
  16. #include "te_effect_dispatch.h"
  17. #include "collisionutils.h"
  18. #include "physobj.h"
  19. #include "world.h"
  20. #include "hierarchy.h"
  21. #include "physics_saverestore.h"
  22. #include "PhysicsCloneArea.h"
  23. #include "portal_gamestats.h"
  24. #include "portal_base2d_shared.h"
  25. #include "weapon_portalgun.h"
  26. #include "portal_placement.h"
  27. #include "physicsshadowclone.h"
  28. #include "particle_parse.h"
  29. #include "rumble_shared.h"
  30. #include "func_portal_orientation.h"
  31. #include "env_debughistory.h"
  32. #include "tier1/callqueue.h"
  33. #include "vphysics/player_controller.h"
  34. #include "saverestore_utlvector.h"
  35. #include "baseprojector.h"
  36. #include "prop_weightedcube.h"
  37. #include "tier0/stackstats.h"
  38. #include "portal2/portal_grabcontroller_shared.h"
  39. // memdbgon must be the last include file in a .cpp file!!!
  40. #include "tier0/memdbgon.h"
  41. extern Vector Portal_FindUsefulVelocity( CBaseEntity *pOther );
  42. ConVar sv_portal_debug_touch("sv_portal_debug_touch", "0", FCVAR_REPLICATED );
  43. ConVar sv_portal_new_velocity_check("sv_portal_new_velocity_check", "1", FCVAR_CHEAT );
  44. ConVar sv_portal_enable_microphone("sv_portal_enable_microphone", "0", FCVAR_DEVELOPMENTONLY );
  45. ConVar sv_portal_microphone_sensitivity ( "sv_portal_microphone_sensitivity", "1.0f" );
  46. ConVar sv_portal_microphone_max_range ( "sv_portal_microphone_max_range", "256.0f" );
  47. ConVar sv_portal_high_speed_physics_early_untouch( "sv_portal_high_speed_physics_early_untouch", "1" );
  48. extern ConVar sv_allow_mobile_portal_teleportation;
  49. const char *CPortal_Base2D::s_szTestRestingSurfaceThinkContext = "CPortal_Base2D::TestRestingSurfaceThink";
  50. const char *CPortal_Base2D::s_szDeactivatePortalNowContext = "CPortal_Base2D::DeactivatePortalNow";
  51. BEGIN_DATADESC( CPortal_Base2D )
  52. //saving
  53. DEFINE_FIELD( m_hLinkedPortal, FIELD_EHANDLE ),
  54. DEFINE_FIELD( m_matrixThisToLinked, FIELD_VMATRIX ),
  55. DEFINE_KEYFIELD( m_bActivated, FIELD_BOOLEAN, "Activated" ),
  56. DEFINE_KEYFIELD( m_bOldActivatedState, FIELD_BOOLEAN, "OldActivated" ),
  57. DEFINE_KEYFIELD( m_bIsPortal2, FIELD_BOOLEAN, "PortalTwo" ),
  58. DEFINE_FIELD( m_vPrevForward, FIELD_VECTOR ),
  59. DEFINE_FIELD( m_hMicrophone, FIELD_EHANDLE ),
  60. DEFINE_FIELD( m_hSpeaker, FIELD_EHANDLE ),
  61. DEFINE_FIELD( m_bMicAndSpeakersLinkedToRemote, FIELD_BOOLEAN ),
  62. DEFINE_FIELD( m_vAudioOrigin, FIELD_VECTOR ),
  63. DEFINE_FIELD( m_vDelayedPosition, FIELD_VECTOR ),
  64. DEFINE_FIELD( m_qDelayedAngles, FIELD_VECTOR ),
  65. DEFINE_FIELD( m_iDelayedFailure, FIELD_INTEGER ),
  66. DEFINE_FIELD( m_vOldPosition, FIELD_VECTOR ),
  67. DEFINE_FIELD( m_qOldAngles, FIELD_VECTOR ),
  68. DEFINE_FIELD( m_hPlacedBy, FIELD_EHANDLE ),
  69. DEFINE_KEYFIELD( m_fNetworkHalfWidth, FIELD_FLOAT, "HalfWidth" ),
  70. DEFINE_KEYFIELD( m_fNetworkHalfHeight, FIELD_FLOAT, "HalfHeight" ),
  71. DEFINE_FIELD( m_bIsMobile, FIELD_BOOLEAN ),
  72. // DEFINE_FIELD( m_plane_Origin, cplane_t ),
  73. // DEFINE_FIELD( m_pAttachedCloningArea, CPhysicsCloneArea ),
  74. // DEFINE_FIELD( m_PortalSimulator, CPortalSimulator ),
  75. // DEFINE_FIELD( m_pCollisionShape, CPhysCollide ),
  76. DEFINE_FIELD( m_bSharedEnvironmentConfiguration, FIELD_BOOLEAN ),
  77. DEFINE_ARRAY( m_vPortalCorners, FIELD_POSITION_VECTOR, 4 ),
  78. DEFINE_UTLVECTOR( m_PortalEventListeners, FIELD_EHANDLE ),
  79. // Function Pointers
  80. DEFINE_THINKFUNC( TestRestingSurfaceThink ),
  81. DEFINE_THINKFUNC( DeactivatePortalNow ),
  82. DEFINE_OUTPUT( m_OnPlacedSuccessfully, "OnPlacedSuccessfully" ),
  83. DEFINE_OUTPUT( m_OnEntityTeleportFromMe, "OnEntityTeleportFromMe" ),
  84. DEFINE_OUTPUT( m_OnPlayerTeleportFromMe, "OnPlayerTeleportFromMe" ),
  85. DEFINE_OUTPUT( m_OnEntityTeleportToMe, "OnEntityTeleportToMe" ),
  86. DEFINE_OUTPUT( m_OnPlayerTeleportToMe, "OnPlayerTeleportToMe" ),
  87. DEFINE_FIELD( m_vPortalSpawnLocation, FIELD_VECTOR ),
  88. END_DATADESC()
  89. extern void SendProxy_Origin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID );
  90. extern void SendProxy_Angles( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID );
  91. IMPLEMENT_SERVERCLASS_ST( CPortal_Base2D, DT_Portal_Base2D )
  92. //upgrade origin and angles to high precision to prevent prediction errors with projected walls.
  93. SendPropExclude( "DT_BaseEntity", "m_vecOrigin" ),
  94. SendPropExclude( "DT_BaseEntity", "m_angRotation" ),
  95. SendPropVector( SENDINFO(m_vecOrigin), -1, SPROP_NOSCALE, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
  96. SendPropVector( SENDINFO(m_angRotation), -1, SPROP_NOSCALE, 0.0f, HIGH_DEFAULT, SendProxy_Angles ),
  97. //if we're resting on another entity, we still need ultra-precise absolute coords. We should probably downgrade local origin/angles in favor of these
  98. SendPropVector( SENDINFO(m_ptOrigin), -1, SPROP_NOSCALE, 0.0f, HIGH_DEFAULT ),
  99. SendPropVector( SENDINFO(m_qAbsAngle), -1, SPROP_NOSCALE, 0.0f, HIGH_DEFAULT ),
  100. SendPropEHandle( SENDINFO(m_hLinkedPortal) ),
  101. SendPropBool( SENDINFO(m_bActivated) ),
  102. SendPropBool( SENDINFO(m_bOldActivatedState) ),
  103. SendPropBool( SENDINFO(m_bIsPortal2) ),
  104. SendPropFloat( SENDINFO( m_fNetworkHalfWidth ), -1, SPROP_NOSCALE, 0.0f, HIGH_DEFAULT ),
  105. SendPropFloat( SENDINFO( m_fNetworkHalfHeight ), -1, SPROP_NOSCALE, 0.0f, HIGH_DEFAULT ),
  106. SendPropBool( SENDINFO( m_bIsMobile ) ),
  107. SendPropDataTable( SENDINFO_DT( m_PortalSimulator ), &REFERENCE_SEND_TABLE( DT_PortalSimulator ) )
  108. END_SEND_TABLE()
  109. LINK_ENTITY_TO_CLASS( portal_base2D, CPortal_Base2D );
  110. CON_COMMAND_F( portal_place, "Places a portal. Indicate the group #, then the portal #, then pos + angle", FCVAR_CHEAT )
  111. {
  112. if ( args.ArgC() != 9 )
  113. {
  114. ConMsg( "Usage: portal_place <group #> <portal #> <pos.x pos.y pos.z> <angle.x angle.y angle.z>\n" );
  115. return;
  116. }
  117. int iLinkageGroupID = atoi( args[1] );
  118. // clamp to either 0, 1 or 2
  119. iLinkageGroupID = MIN( iLinkageGroupID, 2 );
  120. iLinkageGroupID = MAX( iLinkageGroupID, 0 );
  121. bool bPortal2 = atoi( args[2] ) != 0;
  122. CProp_Portal *pPortal = CProp_Portal::FindPortal( iLinkageGroupID, bPortal2, true );
  123. if ( !pPortal )
  124. {
  125. ConMsg( "Error finding portal!\n" );
  126. return;
  127. }
  128. Vector vNewOrigin;
  129. vNewOrigin.x = atof( args[3] );
  130. vNewOrigin.y = atof( args[4] );
  131. vNewOrigin.z = atof( args[5] );
  132. // Next 3 entries are new angles
  133. QAngle vNewAngles;
  134. vNewAngles.x = atof( args[6] );
  135. vNewAngles.y = atof( args[7] );
  136. vNewAngles.z = atof( args[8] );
  137. // Call main placement function (skipping placement rules)
  138. pPortal->NewLocation( vNewOrigin, vNewAngles );
  139. }
  140. void PortalReportFunc( bool bOnlySpewIfPortalsChanged = false )
  141. {
  142. struct PortalReportItem_t
  143. {
  144. Vector vOrigin;
  145. QAngle vAngles;
  146. int nIndex;
  147. int iLinkageGroupID;
  148. };
  149. struct PortalReport_t
  150. {
  151. int nPortals;
  152. int nPlayers;
  153. PortalReportItem_t portals[4];
  154. PortalReportItem_t players[2];
  155. };
  156. static PortalReport_t oldReport = { 0, 0 };
  157. PortalReport_t newReport;
  158. memset( &newReport, 0, sizeof( PortalReport_t ) );
  159. // Portal linkage groups are 0 in single-player or 1 & 2 for first and second player in co-op
  160. for ( int iLinkageGroupID = 0; iLinkageGroupID < 3; ++iLinkageGroupID )
  161. {
  162. for ( int nPortal = 0; nPortal < 2; ++nPortal )
  163. {
  164. CProp_Portal *pPortal = CProp_Portal::FindPortal( iLinkageGroupID, (nPortal != 0), false );
  165. if ( !pPortal )
  166. continue;
  167. PortalReportItem_t &item = newReport.portals[ newReport.nPortals++ ];
  168. item.vOrigin = pPortal->m_ptOrigin;
  169. item.vAngles = pPortal->m_qAbsAngle;
  170. item.nIndex = nPortal;
  171. item.iLinkageGroupID = iLinkageGroupID;
  172. }
  173. }
  174. // Player indices are 1 or 2 (only 1 in single-player)
  175. for ( int i = 1; i <= 2; ++i )
  176. {
  177. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  178. if ( !pPlayer )
  179. continue;
  180. PortalReportItem_t &item = newReport.players[ newReport.nPlayers++ ];
  181. item.vOrigin = pPlayer->GetAbsOrigin();
  182. item.vAngles = pPlayer->GetAbsAngles();
  183. item.nIndex = i;
  184. }
  185. if ( !bOnlySpewIfPortalsChanged || memcmp( &oldReport.portals, &newReport.portals, sizeof( newReport.portals ) ) )
  186. {
  187. for ( int i = 0; i < newReport.nPortals; i++ )
  188. {
  189. PortalReportItem_t &item = newReport.portals[ i ];
  190. ConMsg( "portal_place %d %d %.3f %.3f %.3f %.3f %.3f %.3f; ",
  191. item.iLinkageGroupID, item.nIndex,
  192. item.vOrigin.x, item.vOrigin.y, item.vOrigin.z,
  193. item.vAngles.x, item.vAngles.y, item.vAngles.z );
  194. }
  195. for ( int i = 0; i < newReport.nPlayers; i++ )
  196. {
  197. PortalReportItem_t &item = newReport.players[ i ];
  198. ConMsg( "cmd%d setpos_exact %.3f %.3f %.3f; cmd%d setang_exact %.3f %.3f %.3f; ",
  199. item.nIndex, item.vOrigin.x, item.vOrigin.y, item.vOrigin.z,
  200. item.nIndex, item.vAngles.x, item.vAngles.y, item.vAngles.z );
  201. }
  202. ConMsg( "\n" );
  203. }
  204. memcpy( &oldReport, &newReport, sizeof( PortalReport_t ) );
  205. }
  206. CON_COMMAND_F( portal_report, "Reports the location of all portals", FCVAR_CHEAT )
  207. {
  208. PortalReportFunc();
  209. }
  210. CPortal_Base2D::CPortal_Base2D( void )
  211. {
  212. m_vPrevForward = Vector( 0.0f, 0.0f, 0.0f );
  213. m_PortalSimulator.SetPortalSimulatorCallbacks( this );
  214. // Init to something safe
  215. for ( int i = 0; i < 4; ++i )
  216. {
  217. m_vPortalCorners[i] = Vector(0,0,0);
  218. }
  219. CPortal_Base2D_Shared::AllPortals.AddToTail( this );
  220. // Make sure all listeners are clear
  221. m_PortalEventListeners.Purge();
  222. m_vPortalSpawnLocation.Invalidate();
  223. }
  224. CPortal_Base2D::~CPortal_Base2D( void )
  225. {
  226. CPortal_Base2D_Shared::AllPortals.FindAndRemove( this );
  227. if( m_pCollisionShape )
  228. {
  229. physcollision->DestroyCollide( m_pCollisionShape );
  230. m_pCollisionShape = NULL;
  231. }
  232. }
  233. void CPortal_Base2D::UpdateOnRemove( void )
  234. {
  235. m_PortalSimulator.ClearEverything();
  236. RemovePortalMicAndSpeaker();
  237. CPortal_Base2D *pRemote = m_hLinkedPortal;
  238. if( pRemote != NULL )
  239. {
  240. m_PortalSimulator.DetachFromLinked();
  241. m_hLinkedPortal = NULL;
  242. SetActive( false );
  243. m_bOldActivatedState = false;
  244. pRemote->UpdatePortalLinkage();
  245. pRemote->UpdatePortalTeleportMatrix();
  246. }
  247. if( m_pAttachedCloningArea )
  248. {
  249. UTIL_Remove( m_pAttachedCloningArea );
  250. m_pAttachedCloningArea = NULL;
  251. }
  252. m_PortalEventListeners.Purge();
  253. BaseClass::UpdateOnRemove();
  254. }
  255. void CPortal_Base2D::Spawn( void )
  256. {
  257. Precache();
  258. UpdateCollisionShape();
  259. Assert( (m_fNetworkHalfHeight > 0.0f) && (m_fNetworkHalfWidth > 0.0f) );
  260. m_matrixThisToLinked.Identity(); //don't accidentally teleport objects to zero space
  261. AddEffects( EF_NORECEIVESHADOW | EF_NOSHADOW );
  262. SetSolid( SOLID_OBB );
  263. SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID | FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
  264. SetMoveType( MOVETYPE_NONE );
  265. SetCollisionGroup( COLLISION_GROUP_PLAYER );
  266. SetSize( GetLocalMins(), GetLocalMaxs() );
  267. UpdateCorners();
  268. if( sv_portal_enable_microphone.GetInt() )
  269. {
  270. CreateMicAndSpeaker();
  271. }
  272. BaseClass::Spawn();
  273. m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
  274. m_bMicAndSpeakersLinkedToRemote = false;
  275. // Because we're not solid
  276. AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID );
  277. m_vPortalSpawnLocation = GetAbsOrigin();
  278. }
  279. void CPortal_Base2D::OnRestore()
  280. {
  281. m_ptOrigin = GetAbsOrigin();
  282. m_qAbsAngle = GetAbsAngles();
  283. UpdatePortalTeleportMatrix();
  284. UpdateCorners();
  285. Assert( m_pAttachedCloningArea == NULL );
  286. m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
  287. BaseClass::OnRestore();
  288. }
  289. void DumpActiveCollision( const CPortalSimulator *pPortalSimulator, const char *szFileName );
  290. void PortalSimulatorDumps_DumpCollideToGlView( CPhysCollide *pCollide, const Vector &origin, const QAngle &angles, float fColorScale, const char *pFilename );
  291. bool CPortal_Base2D::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
  292. {
  293. if ( !m_pCollisionShape )
  294. {
  295. //HACK: This is a last-gasp type fix for a crash caused by m_pCollisionShape not yet being set up
  296. // during a restore.
  297. UpdateCollisionShape();
  298. }
  299. physcollision->TraceBox( ray, MASK_ALL, NULL, m_pCollisionShape, GetAbsOrigin(), GetAbsAngles(), &tr );
  300. return tr.DidHit();
  301. }
  302. ConVar portal_test_resting_surface_for_paint( "portal_test_resting_surface_for_paint", "0", 0, "Test if a portal is on a white painted surface and fizzle if it goes away. Test it EVERY FRAME." );
  303. //-----------------------------------------------------------------------------
  304. // Purpose: When placed on a surface that could potentially go away (anything but world geo), we test for that condition and fizzle
  305. //-----------------------------------------------------------------------------
  306. void CPortal_Base2D::TestRestingSurfaceThink( void )
  307. {
  308. // Make sure there's still a surface behind the portal
  309. Vector vOrigin = GetAbsOrigin();
  310. Vector vForward, vRight, vUp;
  311. GetVectors( &vForward, &vRight, &vUp );
  312. trace_t tr;
  313. CTraceFilterSimpleClassnameList baseFilter( NULL, COLLISION_GROUP_NONE );
  314. UTIL_Portal_Trace_Filter( &baseFilter );
  315. baseFilter.AddClassnameToIgnore( "prop_portal" );
  316. CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
  317. int iCornersOnVolatileSurface = 0;
  318. bool bAnyCornerOnPortalPaint = false;
  319. // Check corners
  320. for ( int iCorner = 0; iCorner < 4; ++iCorner )
  321. {
  322. Vector vCorner = vOrigin;
  323. if ( iCorner % 2 == 0 )
  324. vCorner += vRight * ( m_fNetworkHalfWidth - PORTAL_BUMP_FORGIVENESS * 1.1f );
  325. else
  326. vCorner += -vRight * ( m_fNetworkHalfWidth - PORTAL_BUMP_FORGIVENESS * 1.1f );
  327. if ( iCorner < 2 )
  328. vCorner += vUp * ( m_fNetworkHalfHeight - PORTAL_BUMP_FORGIVENESS * 1.1f );
  329. else
  330. vCorner += -vUp * ( m_fNetworkHalfHeight - PORTAL_BUMP_FORGIVENESS * 1.1f );
  331. Ray_t ray;
  332. ray.Init( vCorner, vCorner - vForward );
  333. enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilterPortalShot, &tr );
  334. // This corner isn't on a valid brush (skipping phys converts or physboxes because they frequently go through portals and can't be placed upon).
  335. if ( tr.fraction == 1.0f && !tr.startsolid && ( !tr.m_pEnt || ( tr.m_pEnt && !FClassnameIs( tr.m_pEnt, "func_physbox" ) && !FClassnameIs( tr.m_pEnt, "simple_physics_brush" ) ) ) )
  336. {
  337. DevMsg( "Surface removed from behind portal.\n" );
  338. DeactivatePortalOnThink();
  339. SetContextThink( NULL, TICK_NEVER_THINK, s_szTestRestingSurfaceThinkContext );
  340. break;
  341. }
  342. if ( portal_test_resting_surface_for_paint.GetBool() )
  343. {
  344. PortalSurfaceType_t portalSurfType = PortalSurfaceType( tr );
  345. // check if we still on portal paint
  346. if ( portalSurfType == PORTAL_SURFACE_PAINT )
  347. {
  348. bAnyCornerOnPortalPaint = true;
  349. }
  350. // This corner is on no portal surf without portal paint
  351. if ( portalSurfType == PORTAL_SURFACE_INVALID )
  352. {
  353. DevMsg( "Portal paint removed from behind portal.\n" );
  354. DeactivatePortalOnThink();
  355. SetContextThink( NULL, TICK_NEVER_THINK, s_szTestRestingSurfaceThinkContext );
  356. break;
  357. }
  358. }
  359. if ( !tr.DidHitWorld() )
  360. {
  361. iCornersOnVolatileSurface++;
  362. }
  363. }
  364. // Still on a movable or deletable surface or portal paint
  365. if ( iCornersOnVolatileSurface > 0 || bAnyCornerOnPortalPaint )
  366. {
  367. SetContextThink ( &CPortal_Base2D::TestRestingSurfaceThink, gpGlobals->curtime + 0.1f, s_szTestRestingSurfaceThinkContext );
  368. }
  369. else
  370. {
  371. // All corners on world, we don't need to test
  372. SetContextThink( NULL, TICK_NEVER_THINK, s_szTestRestingSurfaceThinkContext );
  373. }
  374. }
  375. void CPortal_Base2D::DeactivatePortalOnThink( void )
  376. {
  377. if( IsActive() && (GetNextThink( s_szDeactivatePortalNowContext ) == TICK_NEVER_THINK) )
  378. {
  379. SetContextThink( &CPortal_Base2D::DeactivatePortalNow, gpGlobals->curtime, s_szDeactivatePortalNowContext );
  380. }
  381. }
  382. void CPortal_Base2D::DeactivatePortalNow( void )
  383. {
  384. // if held entity is on the opposite side of portal, make the player drops the entity when either portal gets deactivated
  385. for( int i = 1; i <= gpGlobals->maxClients; ++i )
  386. {
  387. CPortal_Player *pPlayer = ToPortalPlayer( UTIL_PlayerByIndex( i ) );
  388. //If the other player exists and is connected
  389. if( pPlayer && pPlayer->IsConnected() && pPlayer->IsUsingVMGrab() )
  390. {
  391. if ( GetPlayerHeldEntity( pPlayer ) && pPlayer->IsHeldObjectOnOppositeSideOfPortal() )
  392. {
  393. CPortal_Base2D *pHeldObjectPortal = pPlayer->GetHeldObjectPortal();
  394. CPortal_Base2D *pPlayerPortal = pHeldObjectPortal->m_hLinkedPortal.Get();
  395. if ( pPlayerPortal == this || pHeldObjectPortal == this )
  396. {
  397. pPlayer->ClearUseEntity();
  398. }
  399. }
  400. }
  401. }
  402. CPortal_Base2D *pRemotePortal = m_hLinkedPortal;
  403. StopParticleEffects( this );
  404. SetActive( false );
  405. m_bOldActivatedState = false;
  406. m_hLinkedPortal = NULL;
  407. m_PortalSimulator.DetachFromLinked();
  408. m_PortalSimulator.ReleaseAllEntityOwnership();
  409. if( pRemotePortal )
  410. {
  411. pRemotePortal->UpdatePortalLinkage();
  412. }
  413. UpdateClientCheckPVS();
  414. SetMoveType( MOVETYPE_NONE );
  415. SetMobileState( false );
  416. SetContextThink( NULL, TICK_NEVER_THINK, s_szDeactivatePortalNowContext );
  417. SetContextThink( NULL, TICK_NEVER_THINK, s_szTestRestingSurfaceThinkContext );
  418. OnPortalDeactivated();
  419. }
  420. //-----------------------------------------------------------------------------
  421. // Purpose: Removes the portal microphone and speakers. This is done in two places
  422. // (fizzle and UpdateOnRemove) so the code is consolidated here.
  423. // Input : -
  424. //-----------------------------------------------------------------------------
  425. void CPortal_Base2D::RemovePortalMicAndSpeaker()
  426. {
  427. // Shut down microphone/speaker if they exist
  428. if ( m_hMicrophone )
  429. {
  430. CEnvMicrophone *pMicrophone = (CEnvMicrophone*)(m_hMicrophone.Get());
  431. if ( pMicrophone )
  432. {
  433. inputdata_t inMicDisable;
  434. pMicrophone->InputDisable( inMicDisable );
  435. UTIL_Remove( pMicrophone );
  436. }
  437. m_hMicrophone = 0;
  438. }
  439. if ( m_hSpeaker )
  440. {
  441. CSpeaker *pSpeaker = (CSpeaker *)(m_hSpeaker.Get());
  442. if ( pSpeaker )
  443. {
  444. // Remove the remote portal's microphone, as it references the speaker we're about to remove.
  445. if ( m_hLinkedPortal.Get() )
  446. {
  447. CPortal_Base2D* pRemotePortal = m_hLinkedPortal.Get();
  448. if ( pRemotePortal->m_hMicrophone )
  449. {
  450. inputdata_t inputdata;
  451. inputdata.pActivator = this;
  452. inputdata.pCaller = this;
  453. CEnvMicrophone* pRemotePortalMic = dynamic_cast<CEnvMicrophone*>(pRemotePortal->m_hMicrophone.Get());
  454. if ( pRemotePortalMic )
  455. {
  456. pRemotePortalMic->Remove();
  457. }
  458. }
  459. }
  460. inputdata_t inTurnOff;
  461. pSpeaker->InputTurnOff( inTurnOff );
  462. UTIL_Remove( pSpeaker );
  463. }
  464. m_hSpeaker = 0;
  465. }
  466. m_bMicAndSpeakersLinkedToRemote = false;
  467. if ( m_hLinkedPortal.Get() )
  468. {
  469. if ( m_hLinkedPortal->m_hMicrophone.Get() )
  470. {
  471. CEnvMicrophone* pRemoteMic = dynamic_cast<CEnvMicrophone*>( m_hLinkedPortal->m_hMicrophone.Get() );
  472. if ( pRemoteMic )
  473. {
  474. pRemoteMic->SetSpeaker( NULL_STRING, NULL );
  475. }
  476. }
  477. m_hLinkedPortal->m_bMicAndSpeakersLinkedToRemote = false;
  478. }
  479. }
  480. void CPortal_Base2D::PunchPenetratingPlayer( CBasePlayer *pPlayer )
  481. {
  482. if ( m_PortalSimulator.IsReadyToSimulate() )
  483. {
  484. ICollideable *pCollideable = pPlayer->GetCollideable();
  485. if ( pCollideable )
  486. {
  487. Vector vMin, vMax;
  488. pCollideable->WorldSpaceSurroundingBounds( &vMin, &vMax );
  489. if ( UTIL_IsBoxIntersectingPortal( ( vMin + vMax ) / 2.0f, ( vMax - vMin ) / 2.0f, this ) )
  490. {
  491. Ray_t playerRay;
  492. playerRay.Init( pPlayer->GetAbsOrigin(), pPlayer->GetAbsOrigin(), pPlayer->GetPlayerMins(), pPlayer->GetPlayerMaxs() );
  493. trace_t WorldTrace;
  494. CTraceFilterSimple traceFilter( pPlayer, COLLISION_GROUP_PLAYER_MOVEMENT );
  495. enginetrace->TraceRay( playerRay, MASK_PLAYERSOLID, &traceFilter, &WorldTrace );
  496. if( WorldTrace.startsolid ) //player would be stuck unless moving using portal traces. Really good indicator that they're actually in the portal plane.
  497. {
  498. Vector vForward;
  499. GetVectors( &vForward, 0, 0 );
  500. vForward *= 100.0f;
  501. pPlayer->VelocityPunch( vForward );
  502. }
  503. }
  504. }
  505. }
  506. }
  507. void CPortal_Base2D::PunchAllPenetratingPlayers( void )
  508. {
  509. for( int i = 1; i <= gpGlobals->maxClients; ++i )
  510. {
  511. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  512. if ( pPlayer )
  513. {
  514. PunchPenetratingPlayer( pPlayer );
  515. }
  516. }
  517. }
  518. void CPortal_Base2D::Activate( void )
  519. {
  520. UpdateCollisionShape();
  521. if( m_pAttachedCloningArea == NULL )
  522. m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
  523. UpdatePortalTeleportMatrix();
  524. UpdatePortalLinkage();
  525. UpdateClientCheckPVS();
  526. BaseClass::Activate();
  527. AddEffects( EF_NOSHADOW | EF_NORECEIVESHADOW );
  528. m_PortalSimulator.SetCarvedParent( GetParent() );
  529. if( IsActive() && (m_hLinkedPortal.Get() != NULL) )
  530. {
  531. Vector ptCenter = GetAbsOrigin();
  532. QAngle qAngles = GetAbsAngles();
  533. SetSize( GetLocalMins(), GetLocalMaxs() );
  534. m_PortalSimulator.SetSize( GetHalfWidth(), GetHalfHeight() );
  535. m_PortalSimulator.MoveTo( ptCenter, qAngles );
  536. //resimulate everything we're touching
  537. touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
  538. if( root )
  539. {
  540. for( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
  541. {
  542. if( (link->flags & FTOUCHLINK_START_TOUCH) == 0 )
  543. continue; //not actually touching this thing
  544. CBaseEntity *pOther = link->entityTouched;
  545. bool bTeleportable = CPortal_Base2D_Shared::IsEntityTeleportable( pOther );
  546. bool bShouldCloneAcrossPortals = CPortal_Base2D_Shared::ShouldPhysicsCloneNonTeleportableEntityAcrossPortals( pOther );
  547. if( bTeleportable || bShouldCloneAcrossPortals )
  548. {
  549. CCollisionProperty *pOtherCollision = pOther->CollisionProp();
  550. Vector vWorldMins, vWorldMaxs;
  551. pOtherCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
  552. Vector ptOtherCenter = (vWorldMins + vWorldMaxs) / 2.0f;
  553. if( m_plane_Origin.normal.Dot( ptOtherCenter ) > m_plane_Origin.dist )
  554. {
  555. //we should be interacting with this object, add it to our environment
  556. if( bTeleportable )
  557. {
  558. if( SharedEnvironmentCheck( pOther ) )
  559. {
  560. Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
  561. (m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
  562. CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
  563. if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
  564. pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
  565. m_PortalSimulator.TakeOwnershipOfEntity( pOther );
  566. }
  567. }
  568. else if( bShouldCloneAcrossPortals )
  569. {
  570. m_PortalSimulator.StartCloningEntityAcrossPortals( pOther );
  571. }
  572. }
  573. }
  574. }
  575. }
  576. }
  577. }
  578. void CPortal_Base2D::Touch( CBaseEntity *pOther )
  579. {
  580. if( pOther->IsPlayer() || (pOther == GetMoveParent()) )
  581. return;
  582. BaseClass::Touch( pOther );
  583. pOther->Touch( this );
  584. // Don't do anything on touch if it's not active
  585. if( !IsActive() || (m_hLinkedPortal.Get() == NULL) )
  586. {
  587. Assert( !m_PortalSimulator.OwnsEntity( pOther ) );
  588. Assert( !pOther->IsPlayer() || (((CPortal_Player *)pOther)->m_hPortalEnvironment.Get() != this) );
  589. //I'd really like to fix the root cause, but this will keep the game going
  590. m_PortalSimulator.ReleaseOwnershipOfEntity( pOther );
  591. m_PortalSimulator.StopCloningEntityAcrossPortals( pOther );
  592. return;
  593. }
  594. Assert( IsMobile() || ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) || (m_hLinkedPortal->IsMobile()) ||
  595. (m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
  596. if( IsMobile() || ((m_hLinkedPortal.Get() != NULL) && m_hLinkedPortal->IsMobile()) )
  597. {
  598. if( !sv_allow_mobile_portal_teleportation.GetBool() || !pOther->IsPlayer() )
  599. return;
  600. }
  601. // Fizzle portal with any moving brush
  602. Vector vVelocityCheck;
  603. AngularImpulse vAngularImpulseCheck;
  604. pOther->GetVelocity( &vVelocityCheck, &vAngularImpulseCheck );
  605. if( vVelocityCheck != vec3_origin || vAngularImpulseCheck != vec3_origin )
  606. {
  607. if ( modelinfo->GetModelType( pOther->GetModel() ) == mod_brush )
  608. {
  609. if ( !FClassnameIs( pOther, "func_physbox" ) && !FClassnameIs( pOther, "simple_physics_brush" ) ) // except CPhysBox
  610. {
  611. Vector vForward;
  612. GetVectors( &vForward, NULL, NULL );
  613. Vector vMin, vMax;
  614. pOther->GetCollideable()->WorldSpaceSurroundingBounds( &vMin, &vMax );
  615. if ( UTIL_IsBoxIntersectingPortal( ( vMin + vMax ) / 2.0f, ( vMax - vMin ) / 2.0f - Vector( 2.0f, 2.0f, 2.0f ), this, 0.0f ) &&
  616. ((pOther->GetSolid() != SOLID_VPHYSICS) || !m_pCollisionShape || UTIL_IsCollideableIntersectingPhysCollide( pOther->GetCollideable(), m_pCollisionShape, m_ptOrigin, m_qAbsAngle )) )
  617. {
  618. DevMsg( "Moving brush intersected portal plane.\n" );
  619. DeactivatePortalOnThink();
  620. return;
  621. }
  622. else
  623. {
  624. Vector vOrigin = GetAbsOrigin();
  625. trace_t tr;
  626. UTIL_TraceLine( vOrigin, vOrigin - vForward * PORTAL_HALF_DEPTH, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
  627. // Something went wrong
  628. if ( tr.fraction == 1.0f && !tr.startsolid )
  629. {
  630. DevMsg( "Surface removed from behind portal.\n" );
  631. DeactivatePortalOnThink();
  632. return;
  633. }
  634. else if ( !sv_allow_mobile_portals.GetBool() && tr.m_pEnt && tr.m_pEnt->IsMoving() )
  635. {
  636. DevMsg( "Surface behind portal is moving.\n" );
  637. DeactivatePortalOnThink();
  638. return;
  639. }
  640. }
  641. }
  642. }
  643. }
  644. if( m_hLinkedPortal == NULL )
  645. return;
  646. if( sv_portal_high_speed_physics_early_untouch.GetBool() )
  647. {
  648. IPhysicsObject *pOtherPhysObject = pOther->VPhysicsGetObject();
  649. if( (pOther->GetMoveType() == MOVETYPE_VPHYSICS) && (pOtherPhysObject != NULL) )
  650. {
  651. Vector vPhysVelocity; //physics velocity not necessarily equal to entity velocity
  652. pOtherPhysObject->GetVelocity( &vPhysVelocity, NULL );
  653. float fExitSpeed = m_plane_Origin.normal.Dot( vPhysVelocity );
  654. if( fExitSpeed > 200.0f ) //200.0f is a magic number indicating "leaving the portal and unlikely to turn back in a single tick"
  655. {
  656. const CPhysCollide *pCollide = pOtherPhysObject->GetCollide();
  657. if( pCollide )
  658. {
  659. //vphysics object leaving portal at a healthy velocity.
  660. //If it's moving fast enough, it will have left our bounds by a large margin before we receive an EndTouch(). It's possible that "large margin" lets the object pass outside
  661. // the area that we carved portal collision. See if we should remove it from the portal environment now instead so it always has valid collision
  662. Vector vPos;
  663. QAngle qAngle;
  664. pOtherPhysObject->GetPosition( &vPos, &qAngle );
  665. float fRadius = physcollision->CollideGetRadius( pCollide );
  666. if( (m_plane_Origin.normal.Dot( vPos ) - fRadius) > m_plane_Origin.dist )
  667. {
  668. //has fully left portal hole, switching collision should be a safe operation
  669. const float fCollisionDataEndsDist = m_PortalSimulator.GetInternalData().Placement.vCollisionCloneExtents.x; //we don't have collision data for the world past this plane!
  670. float fDistToEndPlane = -((m_plane_Origin.normal.Dot( vPos ) - (m_plane_Origin.dist + fCollisionDataEndsDist)) + fRadius);
  671. float fTimeToExit = fDistToEndPlane / fExitSpeed;
  672. if( fTimeToExit < TICK_INTERVAL )
  673. {
  674. //at our current velocity, we will pass outside our carved collision within a tick, need to switch now.
  675. #if 0 //debugging overlays
  676. //projection of where we believe we absolutely must be colliding with the real world as a white OBB
  677. NDebugOverlay::BoxAngles( vPos + vPhysVelocity * fTimeToExit, pOther->CollisionProp()->OBBMins(), pOther->CollisionProp()->OBBMaxs(), qAngle, 255, 255, 255, 64, 30.0f );
  678. //where we are right now as a green OBB
  679. NDebugOverlay::BoxAngles( vPos, pOther->CollisionProp()->OBBMins(), pOther->CollisionProp()->OBBMaxs(), qAngle, 0, 255, 0, 64, 30.0f );
  680. #endif
  681. m_PortalSimulator.ReleaseOwnershipOfEntity( pOther );
  682. return;
  683. }
  684. }
  685. }
  686. }
  687. }
  688. }
  689. bool bTeleportable = CPortal_Base2D_Shared::IsEntityTeleportable( pOther );
  690. //see if we should even be interacting with this object, this is a bugfix where some objects get added to physics environments through walls
  691. if( !IsMobile() && !m_hLinkedPortal->IsMobile() )
  692. {
  693. bool bShouldCloneAcrossPortals = CPortal_Base2D_Shared::ShouldPhysicsCloneNonTeleportableEntityAcrossPortals( pOther );
  694. if( bTeleportable || bShouldCloneAcrossPortals )
  695. {
  696. CCollisionProperty *pOtherCollision = pOther->CollisionProp();
  697. Vector vWorldMins, vWorldMaxs;
  698. pOtherCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
  699. Vector ptOtherCenter = (vWorldMins + vWorldMaxs) / 2.0f;
  700. //hmm, not in our environment, plane tests, sharing tests
  701. if( (m_plane_Origin.normal.Dot( ptOtherCenter ) >= m_plane_Origin.dist) )
  702. {
  703. if( bTeleportable && !m_PortalSimulator.OwnsEntity( pOther ) )
  704. {
  705. if( SharedEnvironmentCheck( pOther ) )
  706. {
  707. CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
  708. if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
  709. pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
  710. m_PortalSimulator.TakeOwnershipOfEntity( pOther );
  711. }
  712. }
  713. else if( bShouldCloneAcrossPortals )
  714. {
  715. m_PortalSimulator.StartCloningEntityAcrossPortals( pOther );
  716. }
  717. }
  718. }
  719. }
  720. if( bTeleportable && ShouldTeleportTouchingEntity( pOther ) &&
  721. (m_PortalSimulator.OwnsEntity( pOther ) || IsMobile() || m_hLinkedPortal->IsMobile()) )
  722. {
  723. TeleportTouchingEntity( pOther );
  724. }
  725. }
  726. void CPortal_Base2D::StartTouch( CBaseEntity *pOther )
  727. {
  728. if( pOther->IsPlayer() || (pOther == GetMoveParent()) )
  729. return;
  730. BaseClass::StartTouch( pOther );
  731. // Since prop_portal is a trigger it doesn't send back start touch, so I'm forcing it
  732. pOther->StartTouch( this );
  733. if( sv_portal_debug_touch.GetBool() )
  734. {
  735. Vector vVelocity = Portal_FindUsefulVelocity( pOther );
  736. DevMsg( "Portal %i StartTouch: %s : %f %f %f : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), vVelocity.x, vVelocity.y, vVelocity.z, gpGlobals->curtime );
  737. }
  738. #if !defined ( DISABLE_DEBUG_HISTORY )
  739. if ( !IsMarkedForDeletion() )
  740. {
  741. ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i StartTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime ) );
  742. }
  743. #endif
  744. if( (m_hLinkedPortal == NULL) || (IsActive() == false) || IsMobile() || m_hLinkedPortal->IsMobile() )
  745. return;
  746. bool bTeleportable = CPortal_Base2D_Shared::IsEntityTeleportable( pOther );
  747. bool bShouldCloneAcrossPortals = CPortal_Base2D_Shared::ShouldPhysicsCloneNonTeleportableEntityAcrossPortals( pOther );
  748. if( bTeleportable || bShouldCloneAcrossPortals )
  749. {
  750. CCollisionProperty *pOtherCollision = pOther->CollisionProp();
  751. Vector vWorldMins, vWorldMaxs;
  752. pOtherCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
  753. Vector ptOtherCenter = (vWorldMins + vWorldMaxs) / 2.0f;
  754. if( m_plane_Origin.normal.Dot( ptOtherCenter ) > m_plane_Origin.dist )
  755. {
  756. //we should be interacting with this object, add it to our environment
  757. if( bTeleportable )
  758. {
  759. if( SharedEnvironmentCheck( pOther ) )
  760. {
  761. Assert( IsMobile() || m_hLinkedPortal->IsMobile() || ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
  762. (m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
  763. CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
  764. if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
  765. pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
  766. m_PortalSimulator.TakeOwnershipOfEntity( pOther );
  767. }
  768. }
  769. else if( bShouldCloneAcrossPortals )
  770. {
  771. m_PortalSimulator.StartCloningEntityAcrossPortals( pOther );
  772. }
  773. }
  774. }
  775. }
  776. void CPortal_Base2D::EndTouch( CBaseEntity *pOther )
  777. {
  778. if ( pOther->IsPlayer() || (pOther == GetMoveParent()) )
  779. return;
  780. BaseClass::EndTouch( pOther );
  781. // Since prop_portal is a trigger it doesn't send back end touch, so I'm forcing it
  782. pOther->EndTouch( this );
  783. // Don't do anything on end touch if it's not active
  784. if ( !IsActive() || IsMobile() || ((m_hLinkedPortal.Get() != NULL) && m_hLinkedPortal->IsMobile()) )
  785. {
  786. return;
  787. }
  788. if ( ShouldTeleportTouchingEntity( pOther ) ) //an object passed through the plane and all the way out of the touch box
  789. {
  790. TeleportTouchingEntity( pOther );
  791. }
  792. else if ( pOther->IsPlayer() && //player
  793. IsCeilingPortal( -0.7071f ) && //most likely falling out of the portal
  794. (m_PortalSimulator.GetInternalData().Placement.PortalPlane.m_Normal.Dot( pOther->WorldSpaceCenter() ) < m_PortalSimulator.GetInternalData().Placement.PortalPlane.m_Dist) && //but behind the portal plane
  795. (((CPortal_Player *)pOther)->m_Local.m_bInDuckJump) ) //while ducking
  796. {
  797. //player has pulled their feet up (moving their center instantaneously) while falling downward out of the portal, send them back (probably only for a frame)
  798. DevMsg( "Player pulled feet above the portal they fell out of, postponing Releasing ownership\n" );
  799. //TeleportTouchingEntity( pOther );
  800. }
  801. else
  802. {
  803. //only 1 of these 2 calls should actually perform work depending on how the entity interacts with portals
  804. m_PortalSimulator.ReleaseOwnershipOfEntity( pOther );
  805. m_PortalSimulator.StopCloningEntityAcrossPortals( pOther );
  806. }
  807. if ( sv_portal_debug_touch.GetBool() )
  808. {
  809. Vector vVelocity = Portal_FindUsefulVelocity( pOther );
  810. DevMsg( "Portal %i EndTouch: %s : %f %f %f : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), vVelocity.x, vVelocity.y, vVelocity.z, gpGlobals->curtime );
  811. }
  812. #if !defined( DISABLE_DEBUG_HISTORY )
  813. if ( !IsMarkedForDeletion() )
  814. {
  815. ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i EndTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime ) );
  816. }
  817. #endif
  818. }
  819. bool CPortal_Base2D::SharedEnvironmentCheck( CBaseEntity *pEntity )
  820. {
  821. Assert( IsMobile() || ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) || (m_hLinkedPortal->IsMobile()) ||
  822. (m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
  823. CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pEntity );
  824. if( (pOwningSimulator == NULL) || (pOwningSimulator == &m_PortalSimulator) )
  825. {
  826. //nobody else is claiming ownership
  827. return true;
  828. }
  829. Vector ptCenter = pEntity->WorldSpaceCenter();
  830. if( (ptCenter - m_PortalSimulator.GetInternalData().Placement.ptCenter).LengthSqr() < (ptCenter - pOwningSimulator->GetInternalData().Placement.ptCenter).LengthSqr() )
  831. return true;
  832. /*if( !m_hLinkedPortal->m_PortalSimulator.EntityIsInPortalHole( pEntity ) )
  833. {
  834. Vector vOtherVelocity;
  835. pEntity->GetVelocity( &vOtherVelocity );
  836. if( vOtherVelocity.Dot( m_PortalSimulator.GetInternalData().Placement.vForward ) < vOtherVelocity.Dot( m_hLinkedPortal->m_PortalSimulator.GetInternalData().Placement.vForward ) )
  837. return true; //entity is going towards this portal more than the other
  838. }*/
  839. return false;
  840. //we're in the shared configuration, and the other portal already owns the object, see if we'd be a better caretaker (distance check
  841. /*CCollisionProperty *pEntityCollision = pEntity->CollisionProp();
  842. Vector vWorldMins, vWorldMaxs;
  843. pEntityCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
  844. Vector ptEntityCenter = (vWorldMins + vWorldMaxs) / 2.0f;
  845. Vector vEntToThis = GetAbsOrigin() - ptEntityCenter;
  846. Vector vEntToRemote = m_hLinkedPortal->GetAbsOrigin() - ptEntityCenter;
  847. return ( vEntToThis.LengthSqr() < vEntToRemote.LengthSqr() );*/
  848. }
  849. void CPortal_Base2D::WakeNearbyEntities( void )
  850. {
  851. CBaseEntity* pList[ 1024 ];
  852. Vector vForward, vUp, vRight;
  853. GetVectors( &vForward, &vRight, &vUp );
  854. Vector ptOrigin = GetAbsOrigin();
  855. QAngle qAngles = GetAbsAngles();
  856. Vector vLocalMins = GetLocalMins();
  857. Vector vLocalMaxs = GetLocalMaxs();
  858. Vector ptOBBStart = ptOrigin;
  859. ptOBBStart += vForward * vLocalMins.x;
  860. ptOBBStart += vRight * vLocalMins.y;
  861. ptOBBStart += vUp * vLocalMins.z;
  862. vForward *= vLocalMaxs.x - vLocalMins.x;
  863. vRight *= vLocalMaxs.y - vLocalMins.y;
  864. vUp *= vLocalMaxs.z - vLocalMins.z;
  865. Vector vAABBMins, vAABBMaxs;
  866. vAABBMins = vAABBMaxs = ptOBBStart;
  867. for( int i = 1; i != 8; ++i )
  868. {
  869. Vector ptTest = ptOBBStart;
  870. if( i & (1 << 0) ) ptTest += vForward;
  871. if( i & (1 << 1) ) ptTest += vRight;
  872. if( i & (1 << 2) ) ptTest += vUp;
  873. if( ptTest.x < vAABBMins.x ) vAABBMins.x = ptTest.x;
  874. if( ptTest.y < vAABBMins.y ) vAABBMins.y = ptTest.y;
  875. if( ptTest.z < vAABBMins.z ) vAABBMins.z = ptTest.z;
  876. if( ptTest.x > vAABBMaxs.x ) vAABBMaxs.x = ptTest.x;
  877. if( ptTest.y > vAABBMaxs.y ) vAABBMaxs.y = ptTest.y;
  878. if( ptTest.z > vAABBMaxs.z ) vAABBMaxs.z = ptTest.z;
  879. }
  880. int count = UTIL_EntitiesInBox( pList, 1024, vAABBMins, vAABBMaxs, 0 );
  881. //Iterate over all the possible targets
  882. for ( int i = 0; i < count; i++ )
  883. {
  884. CBaseEntity *pEntity = pList[i];
  885. if ( pEntity && (pEntity != this) )
  886. {
  887. CCollisionProperty *pEntCollision = pEntity->CollisionProp();
  888. Vector ptEntityCenter = pEntCollision->GetCollisionOrigin();
  889. //double check intersection at the OBB vs OBB level, we don't want to affect large piles of physics objects if we don't have to. It gets slow
  890. if( IsOBBIntersectingOBB( ptOrigin, qAngles, vLocalMins, vLocalMaxs,
  891. ptEntityCenter, pEntCollision->GetCollisionAngles(), pEntCollision->OBBMins(), pEntCollision->OBBMaxs() ) )
  892. {
  893. pEntity->WakeRestingObjects();
  894. //pEntity->SetGroundEntity( NULL );
  895. if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
  896. {
  897. IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
  898. //Check if the reflective cube is in its disabled state and enable it
  899. if ( UTIL_IsReflectiveCube( pEntity ) || UTIL_IsSchrodinger( pEntity ) )
  900. {
  901. CPropWeightedCube *pReflectiveCube = assert_cast<CPropWeightedCube*>( pEntity );
  902. pReflectiveCube->ExitDisabledState();
  903. }
  904. if ( pPhysicsObject && pPhysicsObject->IsMoveable() )
  905. {
  906. pPhysicsObject->Wake();
  907. // If the target is debris, convert it to non-debris
  908. if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
  909. {
  910. // Interactive debris converts back to debris when it comes to rest
  911. pEntity->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
  912. }
  913. }
  914. }
  915. }
  916. }
  917. }
  918. }
  919. void CPortal_Base2D::ForceEntityToFitInPortalWall( CBaseEntity *pEntity )
  920. {
  921. CCollisionProperty *pCollision = pEntity->CollisionProp();
  922. Vector vWorldMins, vWorldMaxs;
  923. pCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
  924. Vector ptCenter = pEntity->WorldSpaceCenter(); //(vWorldMins + vWorldMaxs) / 2.0f;
  925. Vector ptOrigin = pEntity->GetAbsOrigin();
  926. Vector vEntityCenterToOrigin = ptOrigin - ptCenter;
  927. Vector ptPortalCenter = GetAbsOrigin();
  928. Vector vPortalCenterToEntityCenter = ptCenter - ptPortalCenter;
  929. Vector vPortalForward;
  930. GetVectors( &vPortalForward, NULL, NULL );
  931. Vector ptProjectedEntityCenter = ptPortalCenter + ( vPortalForward * vPortalCenterToEntityCenter.Dot( vPortalForward ) );
  932. Vector ptDest;
  933. if ( m_PortalSimulator.IsReadyToSimulate() )
  934. {
  935. Ray_t ray;
  936. ray.Init( ptProjectedEntityCenter, ptCenter, vWorldMins - ptCenter, vWorldMaxs - ptCenter );
  937. trace_t ShortestTrace;
  938. ShortestTrace.fraction = 2.0f;
  939. const PS_SD_Static_Wall_Local_Brushes_t &WallBrushes = m_PortalSimulator.GetInternalData().Simulation.Static.Wall.Local.Brushes;
  940. int iTestMask = MASK_SOLID;
  941. if( pEntity->IsPlayer() )
  942. {
  943. iTestMask |= CONTENTS_PLAYERCLIP;
  944. }
  945. else if( pEntity->IsNPC() )
  946. {
  947. iTestMask |= CONTENTS_MONSTERCLIP;
  948. }
  949. for( int i = 0; i != ARRAYSIZE( WallBrushes.BrushSets ); ++i )
  950. {
  951. if( (WallBrushes.BrushSets[i].iSolidMask & iTestMask) != 0 )
  952. {
  953. trace_t TempTrace;
  954. physcollision->TraceBox( ray, m_PortalSimulator.GetInternalData().Simulation.Static.Wall.Local.Brushes.BrushSets[i].pCollideable, vec3_origin, vec3_angle, &TempTrace );
  955. if( TempTrace.fraction < ShortestTrace.fraction )
  956. {
  957. ShortestTrace = TempTrace;
  958. }
  959. }
  960. }
  961. if( ShortestTrace.fraction < 2.0f )
  962. {
  963. Vector ptNewPos = ShortestTrace.endpos + vEntityCenterToOrigin;
  964. pEntity->Teleport( &ptNewPos, NULL, NULL );
  965. pEntity->AddEffects( EF_NOINTERP );
  966. #if !defined ( DISABLE_DEBUG_HISTORY )
  967. if ( !IsMarkedForDeletion() )
  968. {
  969. ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Teleporting %s inside 'ForceEntityToFitInPortalWall'\n", pEntity->GetDebugName() ) );
  970. }
  971. #endif
  972. if( sv_portal_debug_touch.GetBool() )
  973. {
  974. DevMsg( "Teleporting %s inside 'ForceEntityToFitInPortalWall'\n", pEntity->GetDebugName() );
  975. }
  976. //pEntity->SetAbsOrigin( ShortestTrace.endpos + vEntityCenterToOrigin );
  977. }
  978. }
  979. }
  980. void CPortal_Base2D::UpdatePortalTeleportMatrix( void )
  981. {
  982. //copied from client to ensure the numbers match as closely as possible.
  983. {
  984. ALIGN16 matrix3x4_t finalMatrix;
  985. if( GetMoveParent() )
  986. {
  987. // Construct the entity-to-world matrix
  988. // Start with making an entity-to-parent matrix
  989. ALIGN16 matrix3x4_t matEntityToParent;
  990. AngleMatrix( GetLocalAngles(), matEntityToParent );
  991. MatrixSetColumn( GetLocalOrigin(), 3, matEntityToParent );
  992. // concatenate with our parent's transform
  993. ALIGN16 matrix3x4_t scratchMatrix;
  994. ConcatTransforms( GetParentToWorldTransform( scratchMatrix ), matEntityToParent, finalMatrix );
  995. MatrixGetColumn( finalMatrix, 0, m_vForward );
  996. MatrixGetColumn( finalMatrix, 1, m_vRight );
  997. MatrixGetColumn( finalMatrix, 2, m_vUp );
  998. Vector vTempOrigin;
  999. MatrixGetColumn( finalMatrix, 3, vTempOrigin );
  1000. m_ptOrigin = vTempOrigin;
  1001. m_vRight = -m_vRight;
  1002. QAngle qTempAngle;
  1003. MatrixAngles( finalMatrix, qTempAngle );
  1004. m_qAbsAngle = qTempAngle;
  1005. }
  1006. else
  1007. {
  1008. AngleMatrix( m_qAbsAngle, finalMatrix );
  1009. MatrixGetColumn( finalMatrix, 0, m_vForward );
  1010. MatrixGetColumn( finalMatrix, 1, m_vRight );
  1011. MatrixGetColumn( finalMatrix, 2, m_vUp );
  1012. m_vRight = -m_vRight;
  1013. }
  1014. }
  1015. //setup our origin plane
  1016. m_plane_Origin.normal = m_vForward;
  1017. m_plane_Origin.dist = m_plane_Origin.normal.Dot( m_ptOrigin );
  1018. m_plane_Origin.signbits = SignbitsForPlane( &m_plane_Origin );
  1019. Vector vAbsNormal;
  1020. vAbsNormal.x = fabs(m_plane_Origin.normal.x);
  1021. vAbsNormal.y = fabs(m_plane_Origin.normal.y);
  1022. vAbsNormal.z = fabs(m_plane_Origin.normal.z);
  1023. if( vAbsNormal.x > vAbsNormal.y )
  1024. {
  1025. if( vAbsNormal.x > vAbsNormal.z )
  1026. {
  1027. if( vAbsNormal.x > 0.999f )
  1028. m_plane_Origin.type = PLANE_X;
  1029. else
  1030. m_plane_Origin.type = PLANE_ANYX;
  1031. }
  1032. else
  1033. {
  1034. if( vAbsNormal.z > 0.999f )
  1035. m_plane_Origin.type = PLANE_Z;
  1036. else
  1037. m_plane_Origin.type = PLANE_ANYZ;
  1038. }
  1039. }
  1040. else
  1041. {
  1042. if( vAbsNormal.y > vAbsNormal.z )
  1043. {
  1044. if( vAbsNormal.y > 0.999f )
  1045. m_plane_Origin.type = PLANE_Y;
  1046. else
  1047. m_plane_Origin.type = PLANE_ANYY;
  1048. }
  1049. else
  1050. {
  1051. if( vAbsNormal.z > 0.999f )
  1052. m_plane_Origin.type = PLANE_Z;
  1053. else
  1054. m_plane_Origin.type = PLANE_ANYZ;
  1055. }
  1056. }
  1057. UTIL_Portal_ComputeMatrix( this, m_hLinkedPortal.Get() );
  1058. }
  1059. void CPortal_Base2D::CreateMicAndSpeaker( void )
  1060. {
  1061. RemovePortalMicAndSpeaker();
  1062. inputdata_t inputdata;
  1063. m_hMicrophone = CreateEntityByName( "env_microphone" );
  1064. CEnvMicrophone *pMicrophone = static_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
  1065. pMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED );
  1066. pMicrophone->AddSpawnFlags( SF_MICROPHONE_SOUND_COMBAT | SF_MICROPHONE_SOUND_WORLD | SF_MICROPHONE_SOUND_PLAYER | SF_MICROPHONE_SOUND_BULLET_IMPACT | SF_MICROPHONE_SOUND_EXPLOSION );
  1067. DispatchSpawn( pMicrophone );
  1068. m_hSpeaker = CreateEntityByName( "env_speaker" );
  1069. CSpeaker *pSpeaker = static_cast<CSpeaker*>( m_hSpeaker.Get() );
  1070. float flMicrophoneSensitivity = sv_portal_microphone_sensitivity.GetFloat();
  1071. flMicrophoneSensitivity = clamp( flMicrophoneSensitivity, 0.0f, 10.0f );
  1072. float flMicrophoneRange = sv_portal_microphone_max_range.GetFloat();
  1073. if ( flMicrophoneRange < 0.0f )
  1074. {
  1075. flMicrophoneRange = 0.0f;
  1076. }
  1077. pSpeaker->SetName( MAKE_STRING( m_bIsPortal2 ? "PortalMicrophone_2" : "PortalMicrophone_1" ) );
  1078. pMicrophone->SetName( MAKE_STRING( m_bIsPortal2 ? "PortalSpeaker_2" : "PortalSpeaker_1" ) );
  1079. pMicrophone->Activate();
  1080. pMicrophone->SetSensitivity( flMicrophoneSensitivity );
  1081. pMicrophone->SetMaxRange( flMicrophoneRange );
  1082. // Set microphone/speaker positions
  1083. pMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED );
  1084. pMicrophone->Teleport( &GetAbsOrigin(), &GetAbsAngles(), &vec3_origin );
  1085. pSpeaker->Teleport( &GetAbsOrigin(), &GetAbsAngles(), &vec3_origin );
  1086. }
  1087. void CPortal_Base2D::UpdatePortalLinkage( void )
  1088. {
  1089. if( IsActive() )
  1090. {
  1091. CPortal_Base2D *pLink = m_hLinkedPortal.Get();
  1092. if( pLink != NULL )
  1093. {
  1094. CHandle<CPortal_Base2D> hThis = this;
  1095. CHandle<CPortal_Base2D> hRemote = pLink;
  1096. this->m_hLinkedPortal = hRemote;
  1097. pLink->m_hLinkedPortal = hThis;
  1098. m_bIsPortal2 = !m_hLinkedPortal->m_bIsPortal2;
  1099. // Link up mic and speakers to remote portal
  1100. // NOTE: This does the work for both portals
  1101. if( sv_portal_enable_microphone.GetInt() )
  1102. {
  1103. if ( m_bMicAndSpeakersLinkedToRemote == false )
  1104. {
  1105. // Initialize mics/speakers
  1106. if( m_hMicrophone.Get() == NULL || m_hSpeaker.Get() == NULL )
  1107. {
  1108. CreateMicAndSpeaker();
  1109. }
  1110. if ( m_hLinkedPortal->m_hMicrophone.Get() == NULL || m_hSpeaker.Get() == NULL )
  1111. {
  1112. m_hLinkedPortal->CreateMicAndSpeaker();
  1113. }
  1114. // Cross link the mics and speakers
  1115. CEnvMicrophone* pMyMic = dynamic_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
  1116. CEnvMicrophone* pRemoteMic = dynamic_cast<CEnvMicrophone*>( m_hLinkedPortal->m_hMicrophone.Get() );
  1117. Assert( pMyMic && pRemoteMic && m_hSpeaker.Get() && m_hLinkedPortal->m_hSpeaker.Get() );
  1118. if ( pMyMic )
  1119. {
  1120. pMyMic->SetSpeaker( m_hLinkedPortal->m_hSpeaker->GetEntityName(), m_hLinkedPortal->m_hSpeaker );
  1121. }
  1122. if ( pRemoteMic )
  1123. {
  1124. pRemoteMic->SetSpeaker( m_hSpeaker->GetEntityName(), m_hSpeaker );
  1125. }
  1126. m_bMicAndSpeakersLinkedToRemote = true;
  1127. m_hLinkedPortal->m_bMicAndSpeakersLinkedToRemote = true;
  1128. }
  1129. }
  1130. UpdatePortalTeleportMatrix();
  1131. }
  1132. else
  1133. {
  1134. m_PortalSimulator.DetachFromLinked();
  1135. m_PortalSimulator.ReleaseAllEntityOwnership();
  1136. if ( m_hMicrophone.Get() )
  1137. {
  1138. CEnvMicrophone* pMyMic = dynamic_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
  1139. if ( pMyMic )
  1140. {
  1141. pMyMic->SetSpeaker( NULL_STRING, NULL );
  1142. }
  1143. }
  1144. m_bMicAndSpeakersLinkedToRemote = false;
  1145. }
  1146. m_PortalSimulator.SetSize( GetHalfWidth(), GetHalfHeight() );
  1147. m_PortalSimulator.MoveTo( m_ptOrigin, m_qAbsAngle );
  1148. if( pLink )
  1149. {
  1150. m_PortalSimulator.AttachTo( &pLink->m_PortalSimulator );
  1151. if( IsMobile() || pLink->IsMobile() )
  1152. {
  1153. SetSize( GetLocalMins(), Vector( 4.0f, m_fNetworkHalfWidth, m_fNetworkHalfHeight ) );
  1154. pLink->SetSize( GetLocalMins(), Vector( 4.0f, m_fNetworkHalfWidth, m_fNetworkHalfHeight ) );
  1155. }
  1156. else
  1157. {
  1158. SetSize( GetLocalMins(), GetLocalMaxs() );
  1159. pLink->SetSize( GetLocalMins(), GetLocalMaxs() );
  1160. }
  1161. }
  1162. if( m_pAttachedCloningArea )
  1163. m_pAttachedCloningArea->UpdatePosition();
  1164. }
  1165. else
  1166. {
  1167. CPortal_Base2D *pRemote = m_hLinkedPortal;
  1168. //apparently we've been deactivated
  1169. m_PortalSimulator.DetachFromLinked();
  1170. m_PortalSimulator.ReleaseAllEntityOwnership();
  1171. m_hLinkedPortal = NULL;
  1172. if( pRemote )
  1173. {
  1174. pRemote->UpdatePortalLinkage();
  1175. }
  1176. }
  1177. if( m_bIsPortal2 )
  1178. {
  1179. m_PortalSimulator.EditDebuggingData().overlayColor.SetColor( 255, 0, 0, 255 );
  1180. }
  1181. else
  1182. {
  1183. m_PortalSimulator.EditDebuggingData().overlayColor.SetColor( 0, 0, 255, 255 );
  1184. }
  1185. // 79483: We have a handle on perf now and don't need to dump this every portal placement.
  1186. #if 0
  1187. #if !defined( _CERT )
  1188. // Make sure we have the last state of all portals in the console log, in case of a crash
  1189. PortalReportFunc( true );
  1190. #endif // !_CERT
  1191. #endif
  1192. }
  1193. //#define STACK_ANALYZE_PORTALPLACEMENT
  1194. #if defined( STACK_ANALYZE_PORTALPLACEMENT )
  1195. struct PortalPlacementInfo_t
  1196. {
  1197. DECLARE_CALLSTACKSTATSTRUCT();
  1198. DECLARE_CALLSTACKSTATSTRUCT_FIELDDESCRIPTION();
  1199. int iEntered;
  1200. int iActuallyMoved;
  1201. int iMaxTest;
  1202. int iMinTest;
  1203. };
  1204. BEGIN_STATSTRUCTDESCRIPTION( PortalPlacementInfo_t )
  1205. WRITE_STATSTRUCT_FIELDDESCRIPTION();
  1206. //WRITE_STATSTRUCT_FIELDMERGESCRIPT( SSMSL_Squirrel );
  1207. END_STATSTRUCTDESCRIPTION()
  1208. BEGIN_STATSTRUCTFIELDDESCRIPTION( PortalPlacementInfo_t )
  1209. DEFINE_STATSTRUCTFIELD( iEntered, BasicStatStructFieldDesc, ( BSSFT_INT32, BSSFCM_ADD ) )
  1210. DEFINE_STATSTRUCTFIELD( iActuallyMoved, BasicStatStructFieldDesc, ( BSSFT_INT32, BSSFCM_ADD ) )
  1211. DEFINE_STATSTRUCTFIELD( iMaxTest, BasicStatStructFieldDesc, ( BSSFT_INT32, BSSFCM_MAX ) )
  1212. DEFINE_STATSTRUCTFIELD( iMinTest, BasicStatStructFieldDesc, ( BSSFT_INT32, BSSFCM_MIN ) )
  1213. END_STATSTRUCTFIELDDESCRIPTION()
  1214. CCallStackStatsGatherer<PortalPlacementInfo_t, 32, GetCallStack_Fast> s_PortalPlacementStats;
  1215. CCallStackStatsGatherer_Standardized_t g_PlacementStats = s_PortalPlacementStats;
  1216. void DumpPlacementStats()
  1217. {
  1218. s_PortalPlacementStats.DumpToFile( "PlacementStats.vcsf" );
  1219. }
  1220. static ConCommand dump_placement( "dump_placement", DumpPlacementStats, "", FCVAR_CHEAT );
  1221. #endif
  1222. void CPortal_Base2D::NewLocation( const Vector &vOrigin, const QAngle &qAngles )
  1223. {
  1224. #if defined( STACK_ANALYZE_PORTALPLACEMENT )
  1225. CCallStackStatsGatherer_StructAccessor_AutoLock<PortalPlacementInfo_t> entry = s_PortalPlacementStats.GetEntry();
  1226. ++entry->iEntered;
  1227. entry->iMaxTest = RandomInt(0, 10000);
  1228. entry->iMinTest = RandomInt(0, 10000);
  1229. if( vOrigin != m_ptOrigin || qAngles != m_qAbsAngle )
  1230. {
  1231. ++entry->iActuallyMoved;
  1232. }
  1233. #endif
  1234. // Tell our physics environment to stop simulating it's entities.
  1235. // Fast moving objects can pass through the hole this frame while it's in the old location.
  1236. m_PortalSimulator.ReleaseAllEntityOwnership();
  1237. Vector vOldForward;
  1238. GetVectors( &vOldForward, 0, 0 );
  1239. m_vPrevForward = vOldForward;
  1240. SetParent( NULL );
  1241. SetAbsVelocity( vec3_origin );
  1242. WakeNearbyEntities();
  1243. SetMobileState( false );
  1244. Teleport( &vOrigin, &qAngles, 0 );
  1245. m_ptOrigin = vOrigin;
  1246. m_qAbsAngle = qAngles;
  1247. if ( m_hMicrophone )
  1248. {
  1249. CEnvMicrophone *pMicrophone = static_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
  1250. pMicrophone->Teleport( &vOrigin, &qAngles, 0 );
  1251. inputdata_t inMicEnable;
  1252. pMicrophone->InputEnable( inMicEnable );
  1253. }
  1254. if ( m_hSpeaker )
  1255. {
  1256. CSpeaker *pSpeaker = static_cast<CSpeaker*>( m_hSpeaker.Get() );
  1257. pSpeaker->Teleport( &vOrigin, &qAngles, 0 );
  1258. inputdata_t inTurnOn;
  1259. pSpeaker->InputTurnOn( inTurnOn );
  1260. }
  1261. //if the other portal should be static, let's not punch stuff resting on it
  1262. bool bOtherShouldBeStatic = false;
  1263. if( !m_hLinkedPortal )
  1264. bOtherShouldBeStatic = true;
  1265. if ( IsActive() )
  1266. {
  1267. BroadcastPortalEvent( PORTALEVENT_MOVED );
  1268. }
  1269. SetActive( true );
  1270. UpdatePortalLinkage();
  1271. UpdatePortalTeleportMatrix();
  1272. // Update the four corners of this portal for faster reference
  1273. UpdateCorners();
  1274. WakeNearbyEntities();
  1275. // Make sure it's not a floor portal... allowing those to punch creates a floor to floor exploit
  1276. if ( !IsFloorPortal() )
  1277. {
  1278. if ( m_hLinkedPortal )
  1279. {
  1280. m_hLinkedPortal->WakeNearbyEntities();
  1281. if ( !bOtherShouldBeStatic )
  1282. {
  1283. m_hLinkedPortal->PunchAllPenetratingPlayers();
  1284. }
  1285. }
  1286. }
  1287. UpdateClientCheckPVS();
  1288. CBaseEntity *pAttachedToMovingEntity;
  1289. //check to see if we landed on an entity that could potentially move
  1290. {
  1291. trace_t tr;
  1292. Vector vForward, vUp;
  1293. AngleVectors( qAngles, &vForward, NULL, &vUp );
  1294. UTIL_TraceLine( vOrigin + vForward * 5.0f, vOrigin - vForward * 10.0f, MASK_SOLID_BRUSHONLY, this, COLLISION_GROUP_NONE, &tr );
  1295. if( tr.m_pEnt && ((tr.m_pEnt->GetMoveType() != MOVETYPE_NONE) || (tr.m_pEnt->GetParent() != NULL)) )
  1296. {
  1297. pAttachedToMovingEntity = tr.m_pEnt;
  1298. QAngle qNewAngles; //recompute the angles since they may have changed between when we fired and when we landed
  1299. VectorAngles( vForward, vUp, qNewAngles );
  1300. SetAbsAngles( qNewAngles );
  1301. SetAbsOrigin( tr.endpos );
  1302. m_ptOrigin = tr.endpos;
  1303. m_qAbsAngle = qNewAngles;
  1304. }
  1305. else
  1306. {
  1307. pAttachedToMovingEntity = NULL;
  1308. }
  1309. }
  1310. if( pAttachedToMovingEntity )
  1311. {
  1312. SetMoveType( MOVETYPE_NOCLIP ); //switch the movetype to something that gets CPortal_Base2D::PhysicsSimulate() called.
  1313. SetParent( pAttachedToMovingEntity );
  1314. }
  1315. else
  1316. {
  1317. SetMoveType( MOVETYPE_NONE );
  1318. }
  1319. m_PortalSimulator.SetCarvedParent( pAttachedToMovingEntity );
  1320. SetMobileState( sv_allow_mobile_portals.GetBool() && (pAttachedToMovingEntity != NULL) && UTIL_IsEntityMovingOrRotating( pAttachedToMovingEntity ) );
  1321. m_vPortalSpawnLocation = m_ptOrigin;
  1322. PhysicsTouchTriggers( NULL );
  1323. }
  1324. void CPortal_Base2D::PhysicsSimulate( void )
  1325. {
  1326. BaseClass::PhysicsSimulate();
  1327. //update as if placed in a new position if we're mobile
  1328. bool bMoving = GetParent() && UTIL_IsEntityMovingOrRotating( GetParent() );
  1329. bool bPortalMoving = m_vPortalSpawnLocation.IsValid() && m_vPortalSpawnLocation.DistToSqr( GetAbsOrigin() ) > 0.1f;
  1330. if( ( bMoving || bPortalMoving ) && !sv_allow_mobile_portals.GetBool() )
  1331. {
  1332. DeactivatePortalOnThink();
  1333. SetMoveType( MOVETYPE_NONE );
  1334. SetParent( NULL );
  1335. return;
  1336. }
  1337. SetMobileState( bMoving );
  1338. if( bMoving )
  1339. {
  1340. UpdatePortalLinkage(); //this needs to change names or something. It does so much more than just linkage
  1341. UpdatePortalTeleportMatrix();
  1342. m_PortalSimulator.MoveTo( m_ptOrigin, m_qAbsAngle );
  1343. UpdateCorners();
  1344. #if 0 //mobile portal debugging code
  1345. NDebugOverlay::EntityBounds( this, 0, 255, 0, 50, 10.0f );
  1346. CPortal_Base2D *pLinked = m_hLinkedPortal;
  1347. if( m_hLinkedPortal )
  1348. {
  1349. Vector vTransformedOrigin = pLinked->m_PortalSimulator.m_DataAccess.Placement.matThisToLinked * pLinked->m_PortalSimulator.m_DataAccess.Placement.ptCenter;
  1350. Vector vTransformedForward = pLinked->m_PortalSimulator.m_DataAccess.Placement.matThisToLinked.ApplyRotation( pLinked->m_PortalSimulator.m_DataAccess.Placement.vForward );
  1351. NDebugOverlay::Line( vTransformedOrigin, vTransformedOrigin + vTransformedForward * (-50.0f), 100, 0, 0, true, 10.0f );
  1352. }
  1353. #endif
  1354. }
  1355. }
  1356. void CPortal_Base2D::SetMobileState( bool bSet )
  1357. {
  1358. if( m_bIsMobile != bSet )
  1359. {
  1360. m_bIsMobile = bSet;
  1361. if( bSet )
  1362. {
  1363. //disable physics if it's setup
  1364. //m_PortalSimulator.DetachFromLinked();
  1365. m_PortalSimulator.SetCollisionGenerationEnabled( false );
  1366. m_PortalSimulator.ReleaseAllEntityOwnership();
  1367. if( m_hLinkedPortal.Get() != NULL )
  1368. {
  1369. m_hLinkedPortal->m_PortalSimulator.ReleaseAllEntityOwnership();
  1370. }
  1371. if( IsActive() && (GetNextThink( s_szTestRestingSurfaceThinkContext ) == TICK_NEVER_THINK) )
  1372. SetContextThink( &CPortal_Base2D::TestRestingSurfaceThink, gpGlobals->curtime + 0.1f, s_szTestRestingSurfaceThinkContext );
  1373. }
  1374. else
  1375. {
  1376. //re-enable physics if we're supposed to have it working
  1377. m_PortalSimulator.SetCollisionGenerationEnabled( true );
  1378. if( IsActive() )
  1379. {
  1380. UpdatePortalTeleportMatrix();
  1381. m_PortalSimulator.MoveTo( m_ptOrigin, m_qAbsAngle );
  1382. m_PortalSimulator.SetSize( GetHalfWidth(), GetHalfHeight() );
  1383. CPortal_Base2D *pLink = m_hLinkedPortal.Get();
  1384. if( pLink && !pLink->m_bIsMobile )
  1385. m_PortalSimulator.AttachTo( &pLink->m_PortalSimulator );
  1386. }
  1387. }
  1388. bool bSmallSize = bSet;
  1389. CPortal_Base2D *pLinked = m_hLinkedPortal;
  1390. if( pLinked )
  1391. {
  1392. bSmallSize |= pLinked->IsMobile();
  1393. pLinked->SetSize( pLinked->GetLocalMins(), bSmallSize ? Vector( 4.0f, pLinked->m_fNetworkHalfWidth, pLinked->m_fNetworkHalfHeight ) : pLinked->GetLocalMaxs() );
  1394. }
  1395. SetSize( GetLocalMins(), bSmallSize ? Vector( 4.0f, m_fNetworkHalfWidth, m_fNetworkHalfHeight ) : GetLocalMaxs() );
  1396. }
  1397. }
  1398. void CPortal_Base2D::Resize( float fHalfWidth, float fHalfHeight )
  1399. {
  1400. if( (fHalfWidth == m_fNetworkHalfWidth) && (fHalfHeight == m_fNetworkHalfHeight) )
  1401. return;
  1402. m_fNetworkHalfWidth = fHalfWidth;
  1403. m_fNetworkHalfHeight = fHalfHeight;
  1404. CPortal_Base2D *pLinked = m_hLinkedPortal;
  1405. if( pLinked )
  1406. {
  1407. if( (m_fNetworkHalfWidth != pLinked->m_fNetworkHalfWidth) || (m_fNetworkHalfHeight != pLinked->m_fNetworkHalfHeight) )
  1408. {
  1409. //different portal sizes, unsupported, unlink. Scaling is a whole different ball of wax.
  1410. //if you're resizing both portals. They'll find eachother in UpdatePortalLinkage() once they're both resized.
  1411. m_hLinkedPortal = NULL;
  1412. m_PortalSimulator.DetachFromLinked();
  1413. pLinked->m_hLinkedPortal = NULL;
  1414. pLinked->m_PortalSimulator.DetachFromLinked();
  1415. }
  1416. }
  1417. UpdateCollisionShape();
  1418. if( m_pAttachedCloningArea )
  1419. m_pAttachedCloningArea->Resize( fHalfWidth, fHalfHeight );
  1420. m_PortalSimulator.SetSize( fHalfWidth, fHalfHeight );
  1421. if( pLinked )
  1422. pLinked->UpdatePortalLinkage();
  1423. UpdatePortalLinkage();
  1424. CBaseProjector::TestAllForProjectionChanges();
  1425. }
  1426. void CPortal_Base2D::UpdateClientCheckPVS( void )
  1427. {
  1428. for ( int i = 1; i <= gpGlobals->maxClients; ++i )
  1429. {
  1430. CPortal_Player *pPlayer = dynamic_cast<CPortal_Player*>(UTIL_PlayerByIndex( i ));
  1431. if ( pPlayer == NULL )
  1432. continue;
  1433. pPlayer->MarkClientCheckPVSDirty();
  1434. }
  1435. }
  1436. void CPortal_Base2D::UpdateCorners()
  1437. {
  1438. Vector vOrigin = m_ptOrigin;
  1439. Vector vUp, vRight;
  1440. GetVectors( NULL, &vRight, &vUp );
  1441. vRight *= m_fNetworkHalfWidth;
  1442. vUp *= m_fNetworkHalfHeight;
  1443. m_vPortalCorners[0] = (vOrigin + vRight) + vUp;
  1444. m_vPortalCorners[1] = (vOrigin - vRight) + vUp;
  1445. m_vPortalCorners[2] = (vOrigin - vRight) - vUp;
  1446. m_vPortalCorners[3] = (vOrigin + vRight) - vUp;
  1447. }
  1448. //-----------------------------------------------------------------------------
  1449. // Purpose: Tell all listeners about an event that just occurred
  1450. //-----------------------------------------------------------------------------
  1451. void CPortal_Base2D::OnPortalDeactivated( void )
  1452. {
  1453. BroadcastPortalEvent( PORTALEVENT_FIZZLE );
  1454. CBaseProjector::TestAllForProjectionChanges();
  1455. }
  1456. //-----------------------------------------------------------------------------
  1457. // Purpose: Tell all listeners about an event that just occurred
  1458. //-----------------------------------------------------------------------------
  1459. void CPortal_Base2D::BroadcastPortalEvent( PortalEvent_t nEventType )
  1460. {
  1461. /*
  1462. switch( nEventType )
  1463. {
  1464. case PORTALEVENT_MOVED:
  1465. Msg("[ Portal moved ]\n");
  1466. break;
  1467. case PORTALEVENT_FIZZLE:
  1468. Msg("[ Portal fizzled ]\n");
  1469. break;
  1470. case PORTALEVENT_LINKED:
  1471. Msg("[ Portal linked ]\n");
  1472. break;
  1473. }
  1474. */
  1475. // We need to walk the list backwards because callers can remove themselves from our list as they're notified
  1476. for ( int i = m_PortalEventListeners.Count()-1; i >= 0; i-- )
  1477. {
  1478. if ( m_PortalEventListeners[i] == NULL )
  1479. continue;
  1480. m_PortalEventListeners[i]->NotifyPortalEvent( nEventType, this );
  1481. }
  1482. }
  1483. //-----------------------------------------------------------------------------
  1484. // Purpose: Add a listener to our collection
  1485. //-----------------------------------------------------------------------------
  1486. void CPortal_Base2D::AddPortalEventListener( EHANDLE hListener )
  1487. {
  1488. // Don't multiply add
  1489. if ( m_PortalEventListeners.Find( hListener ) != m_PortalEventListeners.InvalidIndex() )
  1490. return;
  1491. m_PortalEventListeners.AddToTail( hListener );
  1492. }
  1493. //-----------------------------------------------------------------------------
  1494. // Purpose: Remove a listener to our collection
  1495. //-----------------------------------------------------------------------------
  1496. void CPortal_Base2D::RemovePortalEventListener( EHANDLE hListener )
  1497. {
  1498. m_PortalEventListeners.FindAndFastRemove( hListener );
  1499. }
  1500. // Adds the PVS of the cluster where the portal's partner is placed to the parameter PVS.
  1501. // NOTE: adds the *LINKED* portal's cluster, not the parameter portal.
  1502. void AddPortalVisibilityToPVS( CPortal_Base2D* pPortal, int outputpvslength, unsigned char *outputpvs )
  1503. {
  1504. Assert( pPortal );
  1505. if ( pPortal && pPortal->IsActivedAndLinked() )
  1506. {
  1507. CPortal_Base2D* pLinked = pPortal->m_hLinkedPortal.Get();
  1508. int iCluster = engine->GetClusterForOrigin( pLinked->GetAbsOrigin() );
  1509. // get the pvs for the linked portal's cluster
  1510. byte pvs[MAX_MAP_LEAFS/8];
  1511. engine->GetPVSForCluster( iCluster, sizeof( pvs ), pvs );
  1512. // Do the bulk on blocks of 4
  1513. uint32 nDWords = outputpvslength / 4;
  1514. uint32 *pInputDWords = (uint32*)pvs;
  1515. uint32 *RESTRICT pOutputDWords = (uint32*)outputpvs;
  1516. for ( int i=0; i<nDWords; ++i )
  1517. {
  1518. pOutputDWords[ i ] |= pInputDWords[ i ];
  1519. }
  1520. // Do the remaining (up to 3) in bytes
  1521. for ( int i=nDWords * 4; i<outputpvslength; ++i )
  1522. {
  1523. outputpvs[ i ] |= pvs[ i ];
  1524. }
  1525. }
  1526. }
  1527. CServerNetworkProperty *CPortal_Base2D::GetExtenderNetworkProp( void )
  1528. {
  1529. return NetworkProp();
  1530. }
  1531. const edict_t *CPortal_Base2D::GetExtenderEdict( void ) const
  1532. {
  1533. return edict();
  1534. }
  1535. Vector CPortal_Base2D::GetExtensionPVSOrigin( void )
  1536. {
  1537. return GetAbsOrigin() + m_plane_Origin.normal;
  1538. }
  1539. bool CPortal_Base2D::IsExtenderValid( void )
  1540. {
  1541. return IsActivedAndLinked();
  1542. }
  1543. int CPortal_Base2D::GetPolyVertCount( void )
  1544. {
  1545. return 4;
  1546. }
  1547. int CPortal_Base2D::ComputeFrustumThroughPolygon( const Vector &vVisOrigin, const VPlane *pInputFrustum, int iInputFrustumPlanes, VPlane *pOutputFrustum, int iOutputFrustumMaxPlanes )
  1548. {
  1549. int iReturnedPlanes = UTIL_CalcFrustumThroughConvexPolygon( m_vPortalCorners, 4, vVisOrigin, pInputFrustum, iInputFrustumPlanes, pOutputFrustum, iOutputFrustumMaxPlanes, 0 );
  1550. if( (iReturnedPlanes < iOutputFrustumMaxPlanes) && (iReturnedPlanes != 0) )
  1551. {
  1552. //add the portal plane as a near plane
  1553. pOutputFrustum[iReturnedPlanes].Init( -m_plane_Origin.normal, -m_plane_Origin.dist );
  1554. ++iReturnedPlanes;
  1555. }
  1556. return iReturnedPlanes;
  1557. }
  1558. //////////////////////////////////////////////////////////////////////////
  1559. // AddPortalCornersToEnginePVS
  1560. // Subroutine to wrap the adding of portal corners to the PVS which is called once for the setup of each portal.
  1561. // input - pPortal: the portal we are viewing 'out of' which needs it's corners added to the PVS
  1562. //////////////////////////////////////////////////////////////////////////
  1563. void AddPortalCornersToEnginePVS( CPortal_Base2D* pPortal )
  1564. {
  1565. Assert ( pPortal );
  1566. if ( !pPortal )
  1567. return;
  1568. Vector vForward, vRight, vUp;
  1569. pPortal->GetVectors( &vForward, &vRight, &vUp );
  1570. // Center of the remote portal
  1571. Vector ptOrigin = pPortal->GetAbsOrigin();
  1572. // Distance offsets to the different edges of the portal... Used in the placement checks
  1573. Vector vToTopEdge = vUp * ( pPortal->GetHalfHeight() - PORTAL_BUMP_FORGIVENESS );
  1574. Vector vToBottomEdge = -vToTopEdge;
  1575. Vector vToRightEdge = vRight * ( pPortal->GetHalfWidth() - PORTAL_BUMP_FORGIVENESS );
  1576. Vector vToLeftEdge = -vToRightEdge;
  1577. // Distance to place PVS points away from portal, to avoid being in solid
  1578. Vector vForwardBump = vForward * 1.0f;
  1579. // Add center and edges to the engine PVS
  1580. engine->AddOriginToPVS( ptOrigin + vForwardBump);
  1581. engine->AddOriginToPVS( ptOrigin + vToTopEdge + vToLeftEdge + vForwardBump );
  1582. engine->AddOriginToPVS( ptOrigin + vToTopEdge + vToRightEdge + vForwardBump );
  1583. engine->AddOriginToPVS( ptOrigin + vToBottomEdge + vToLeftEdge + vForwardBump );
  1584. engine->AddOriginToPVS( ptOrigin + vToBottomEdge + vToRightEdge + vForwardBump );
  1585. }
  1586. void CPortal_Base2D::ComputeSubVisibility( CPVS_Extender **pExtenders, int iExtenderCount, unsigned char *outputPVS, int pvssize, const Vector &vVisOrigin, const VPlane *pVisFrustum, int iVisFrustumPlanes, VisExtensionChain_t *pVisChain, int iAreasNetworked[MAX_MAP_AREAS], int iMaxRecursionsLeft )
  1587. {
  1588. if( iAreasNetworked[MAX_MAP_AREAS - 1] != -1 ) //early out, can't add any more data if we wanted to
  1589. return;
  1590. if( m_plane_Origin.normal.Dot( vVisOrigin ) < m_plane_Origin.dist )
  1591. return; //vis origin is behind the portal plane
  1592. //both test if the portal is within the view frustum, and calculate the new one at the same time
  1593. int iFrustumPlanesMax = (iVisFrustumPlanes + GetPolyVertCount() + 1);
  1594. VPlane *pNewFrustum = (VPlane *)stackalloc( sizeof( VPlane ) * iFrustumPlanesMax );
  1595. int iNewFrustumPlanes = ComputeFrustumThroughPolygon( vVisOrigin, pVisFrustum, iVisFrustumPlanes, pNewFrustum, iFrustumPlanesMax );
  1596. if( iNewFrustumPlanes == 0 )
  1597. {
  1598. //NDebugOverlay::EntityBounds( this, 255, 0, 0, 100, 0.0f );
  1599. return;
  1600. }
  1601. //NDebugOverlay::EntityBounds( this, 0, 255, 0, 100, 0.0f );
  1602. CPortal_Base2D *pLinkedPortal = m_hLinkedPortal.Get();
  1603. int iLinkedArea = pLinkedPortal->NetworkProp()->AreaNum();
  1604. unsigned char *pLinkedPVS = pLinkedPortal->m_pExtenderData->iPVSBits;
  1605. if( !pLinkedPortal->m_pExtenderData->bAddedToPVSAlready )
  1606. {
  1607. bool bFound = false;
  1608. for( int i = 0; i != MAX_MAP_AREAS; ++i )
  1609. {
  1610. if( iAreasNetworked[i] == iLinkedArea )
  1611. {
  1612. bFound = true;
  1613. break;
  1614. }
  1615. if( iAreasNetworked[i] == -1 )
  1616. {
  1617. bFound = true; //we found it by adding it
  1618. iAreasNetworked[i] = iLinkedArea;
  1619. int iOutputPVSIntSize = pvssize / sizeof( unsigned int );
  1620. for( int j = 0; j != iOutputPVSIntSize; ++j )
  1621. {
  1622. ((unsigned int *)outputPVS)[j] |= ((unsigned int *)pLinkedPVS)[j];
  1623. }
  1624. for( int j = iOutputPVSIntSize * sizeof( unsigned int ); j != pvssize; ++j )
  1625. {
  1626. outputPVS[j] |= pLinkedPVS[j];
  1627. }
  1628. break;
  1629. }
  1630. }
  1631. AddPortalCornersToEnginePVS( pLinkedPortal );
  1632. pLinkedPortal->m_pExtenderData->bAddedToPVSAlready = true;
  1633. }
  1634. --iMaxRecursionsLeft;
  1635. if( iMaxRecursionsLeft == 0 )
  1636. return;
  1637. edict_t *linkedPortalEdict = pLinkedPortal->edict();
  1638. VisExtensionChain_t chainNode;
  1639. chainNode.m_nArea = iLinkedArea;
  1640. chainNode.pParentChain = pVisChain;
  1641. //transform vis origin to linked space
  1642. Vector vTransformedVisOrigin = m_matrixThisToLinked * vVisOrigin;
  1643. Vector vTranslation = m_matrixThisToLinked.GetTranslation();
  1644. //transform the planes into the linked portal space
  1645. for( int i = 0; i != iNewFrustumPlanes; ++i )
  1646. {
  1647. pNewFrustum[i].m_Normal = m_matrixThisToLinked.ApplyRotation( pNewFrustum[i].m_Normal );
  1648. pNewFrustum[i].m_Dist += pNewFrustum[i].m_Normal.Dot( vTranslation );
  1649. }
  1650. Assert( pLinkedPVS != NULL );
  1651. //extend the vis by what the linked portal can see
  1652. for( int i = 0; i != iExtenderCount; ++i )
  1653. {
  1654. CPVS_Extender *pExtender = pExtenders[i];
  1655. if ( pExtender->GetExtenderEdict() == linkedPortalEdict )
  1656. continue;
  1657. if ( pExtender->GetExtenderNetworkProp()->IsInPVS( linkedPortalEdict, pLinkedPVS, (MAX_MAP_LEAFS/8) ) ) //test against linked portal PVS, not aggregate PVS
  1658. {
  1659. chainNode.pExtender = pExtender;
  1660. pExtender->ComputeSubVisibility( pExtenders, iExtenderCount, outputPVS, pvssize, vTransformedVisOrigin, pNewFrustum, iNewFrustumPlanes, &chainNode, iAreasNetworked, iMaxRecursionsLeft );
  1661. }
  1662. }
  1663. }
  1664. //-----------------------------------------------------------------------------
  1665. // Purpose: Notify this the supplied entity has teleported to this portal
  1666. //-----------------------------------------------------------------------------
  1667. void CPortal_Base2D::OnEntityTeleportedToPortal( CBaseEntity *pEntity )
  1668. {
  1669. m_OnEntityTeleportToMe.FireOutput( this, this );
  1670. BroadcastPortalEvent( PORTALEVENT_ENTITY_TELEPORTED_TO );
  1671. if ( pEntity->IsPlayer() )
  1672. {
  1673. m_OnPlayerTeleportToMe.FireOutput( this, this );
  1674. BroadcastPortalEvent( PORTALEVENT_PLAYER_TELEPORTED_TO );
  1675. }
  1676. }
  1677. //-----------------------------------------------------------------------------
  1678. // Purpose: Notify this the supplied entity has teleported from this portal
  1679. //-----------------------------------------------------------------------------
  1680. void CPortal_Base2D::OnEntityTeleportedFromPortal( CBaseEntity *pEntity )
  1681. {
  1682. m_OnEntityTeleportFromMe.FireOutput( this, this );
  1683. BroadcastPortalEvent( PORTALEVENT_ENTITY_TELEPORTED_FROM );
  1684. if ( pEntity->IsPlayer() )
  1685. {
  1686. m_OnPlayerTeleportFromMe.FireOutput( this, this );
  1687. BroadcastPortalEvent( PORTALEVENT_PLAYER_TELEPORTED_FROM );
  1688. }
  1689. }
  1690. void EntityPortalled( CPortal_Base2D *pPortal, CBaseEntity *pOther, const Vector &vNewOrigin, const QAngle &qNewAngles, bool bForcedDuck )
  1691. {
  1692. /*if( pOther->IsPlayer() )
  1693. {
  1694. Warning( "Server player portalled %f %f %f %f %f %f %f\n", gpGlobals->curtime, XYZ( vNewOrigin ), XYZ( ((CPortal_Player *)pOther)->pl.v_angle ) );
  1695. }*/
  1696. for( int i = 1; i <= gpGlobals->maxClients; ++i )
  1697. {
  1698. CPortal_Player *pPlayer = (CPortal_Player *)UTIL_PlayerByIndex( i );
  1699. if( pPlayer )
  1700. {
  1701. pPlayer->NetworkPortalTeleportation( pOther, pPortal, gpGlobals->curtime, bForcedDuck );
  1702. }
  1703. }
  1704. }
  1705. void DebugPortalCollideables_f( const CCommand &command )
  1706. {
  1707. for( int i = 0; i != CPortal_Base2D_Shared::AllPortals.Count(); ++i )
  1708. {
  1709. CPortal_Base2D *pPortal = CPortal_Base2D_Shared::AllPortals[i];
  1710. pPortal->m_PortalSimulator.DebugCollisionOverlay( true, 30.0f );
  1711. }
  1712. }
  1713. static ConCommand debugportalcollideables("debugportalcollideables", DebugPortalCollideables_f, "Dump all CPhysCollides for all portals to the debug overlay" );