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

2315 lines
73 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #include "cbase.h"
  8. #include "prop_portal.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 "prop_portal_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. // memdbgon must be the last include file in a .cpp file!!!
  34. #include "tier0/memdbgon.h"
  35. #define MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY 50.0f
  36. #define MINIMUM_FLOOR_TO_FLOOR_PORTAL_EXIT_VELOCITY 225.0f
  37. #define MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY_PLAYER 300.0f
  38. #define MAXIMUM_PORTAL_EXIT_VELOCITY 1000.0f
  39. CCallQueue *GetPortalCallQueue();
  40. ConVar sv_portal_debug_touch("sv_portal_debug_touch", "0", FCVAR_REPLICATED );
  41. ConVar sv_portal_placement_never_fail("sv_portal_placement_never_fail", "0", FCVAR_REPLICATED | FCVAR_CHEAT );
  42. ConVar sv_portal_new_velocity_check("sv_portal_new_velocity_check", "1", FCVAR_CHEAT );
  43. static CUtlVector<CProp_Portal *> s_PortalLinkageGroups[256];
  44. BEGIN_DATADESC( CProp_Portal )
  45. //saving
  46. DEFINE_FIELD( m_hLinkedPortal, FIELD_EHANDLE ),
  47. DEFINE_KEYFIELD( m_iLinkageGroupID, FIELD_CHARACTER, "LinkageGroupID" ),
  48. DEFINE_FIELD( m_matrixThisToLinked, FIELD_VMATRIX ),
  49. DEFINE_KEYFIELD( m_bActivated, FIELD_BOOLEAN, "Activated" ),
  50. DEFINE_KEYFIELD( m_bIsPortal2, FIELD_BOOLEAN, "PortalTwo" ),
  51. DEFINE_FIELD( m_vPrevForward, FIELD_VECTOR ),
  52. DEFINE_FIELD( m_hMicrophone, FIELD_EHANDLE ),
  53. DEFINE_FIELD( m_hSpeaker, FIELD_EHANDLE ),
  54. DEFINE_SOUNDPATCH( m_pAmbientSound ),
  55. DEFINE_FIELD( m_vAudioOrigin, FIELD_VECTOR ),
  56. DEFINE_FIELD( m_vDelayedPosition, FIELD_VECTOR ),
  57. DEFINE_FIELD( m_qDelayedAngles, FIELD_VECTOR ),
  58. DEFINE_FIELD( m_iDelayedFailure, FIELD_INTEGER ),
  59. DEFINE_FIELD( m_hPlacedBy, FIELD_EHANDLE ),
  60. // DEFINE_FIELD( m_plane_Origin, cplane_t ),
  61. // DEFINE_FIELD( m_pAttachedCloningArea, CPhysicsCloneArea ),
  62. // DEFINE_FIELD( m_PortalSimulator, CPortalSimulator ),
  63. // DEFINE_FIELD( m_pCollisionShape, CPhysCollide ),
  64. DEFINE_FIELD( m_bSharedEnvironmentConfiguration, FIELD_BOOLEAN ),
  65. DEFINE_ARRAY( m_vPortalCorners, FIELD_POSITION_VECTOR, 4 ),
  66. // Function Pointers
  67. DEFINE_THINKFUNC( DelayedPlacementThink ),
  68. DEFINE_THINKFUNC( TestRestingSurfaceThink ),
  69. DEFINE_THINKFUNC( FizzleThink ),
  70. DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetActivatedState", InputSetActivatedState ),
  71. DEFINE_INPUTFUNC( FIELD_VOID, "Fizzle", InputFizzle ),
  72. DEFINE_INPUTFUNC( FIELD_STRING, "NewLocation", InputNewLocation ),
  73. DEFINE_OUTPUT( m_OnPlacedSuccessfully, "OnPlacedSuccessfully" ),
  74. END_DATADESC()
  75. IMPLEMENT_SERVERCLASS_ST( CProp_Portal, DT_Prop_Portal )
  76. SendPropEHandle( SENDINFO(m_hLinkedPortal) ),
  77. SendPropBool( SENDINFO(m_bActivated) ),
  78. SendPropBool( SENDINFO(m_bIsPortal2) ),
  79. END_SEND_TABLE()
  80. LINK_ENTITY_TO_CLASS( prop_portal, CProp_Portal );
  81. CProp_Portal::CProp_Portal( void )
  82. {
  83. m_vPrevForward = Vector( 0.0f, 0.0f, 0.0f );
  84. m_PortalSimulator.SetPortalSimulatorCallbacks( this );
  85. // Init to something safe
  86. for ( int i = 0; i < 4; ++i )
  87. {
  88. m_vPortalCorners[i] = Vector(0,0,0);
  89. }
  90. //create the collision shape.... TODO: consider having one shared collideable between all portals
  91. float fPlanes[6*4];
  92. fPlanes[(0*4) + 0] = 1.0f;
  93. fPlanes[(0*4) + 1] = 0.0f;
  94. fPlanes[(0*4) + 2] = 0.0f;
  95. fPlanes[(0*4) + 3] = CProp_Portal_Shared::vLocalMaxs.x;
  96. fPlanes[(1*4) + 0] = -1.0f;
  97. fPlanes[(1*4) + 1] = 0.0f;
  98. fPlanes[(1*4) + 2] = 0.0f;
  99. fPlanes[(1*4) + 3] = -CProp_Portal_Shared::vLocalMins.x;
  100. fPlanes[(2*4) + 0] = 0.0f;
  101. fPlanes[(2*4) + 1] = 1.0f;
  102. fPlanes[(2*4) + 2] = 0.0f;
  103. fPlanes[(2*4) + 3] = CProp_Portal_Shared::vLocalMaxs.y;
  104. fPlanes[(3*4) + 0] = 0.0f;
  105. fPlanes[(3*4) + 1] = -1.0f;
  106. fPlanes[(3*4) + 2] = 0.0f;
  107. fPlanes[(3*4) + 3] = -CProp_Portal_Shared::vLocalMins.y;
  108. fPlanes[(4*4) + 0] = 0.0f;
  109. fPlanes[(4*4) + 1] = 0.0f;
  110. fPlanes[(4*4) + 2] = 1.0f;
  111. fPlanes[(4*4) + 3] = CProp_Portal_Shared::vLocalMaxs.z;
  112. fPlanes[(5*4) + 0] = 0.0f;
  113. fPlanes[(5*4) + 1] = 0.0f;
  114. fPlanes[(5*4) + 2] = -1.0f;
  115. fPlanes[(5*4) + 3] = -CProp_Portal_Shared::vLocalMins.z;
  116. CPolyhedron *pPolyhedron = GeneratePolyhedronFromPlanes( fPlanes, 6, 0.00001f, true );
  117. Assert( pPolyhedron != NULL );
  118. CPhysConvex *pConvex = physcollision->ConvexFromConvexPolyhedron( *pPolyhedron );
  119. pPolyhedron->Release();
  120. Assert( pConvex != NULL );
  121. m_pCollisionShape = physcollision->ConvertConvexToCollide( &pConvex, 1 );
  122. CProp_Portal_Shared::AllPortals.AddToTail( this );
  123. }
  124. CProp_Portal::~CProp_Portal( void )
  125. {
  126. CProp_Portal_Shared::AllPortals.FindAndRemove( this );
  127. s_PortalLinkageGroups[m_iLinkageGroupID].FindAndRemove( this );
  128. }
  129. void CProp_Portal::UpdateOnRemove( void )
  130. {
  131. m_PortalSimulator.ClearEverything();
  132. RemovePortalMicAndSpeaker();
  133. CProp_Portal *pRemote = m_hLinkedPortal;
  134. if( pRemote != NULL )
  135. {
  136. m_PortalSimulator.DetachFromLinked();
  137. m_hLinkedPortal = NULL;
  138. m_bActivated = false;
  139. pRemote->UpdatePortalLinkage();
  140. pRemote->UpdatePortalTeleportMatrix();
  141. }
  142. if( m_pAttachedCloningArea )
  143. {
  144. UTIL_Remove( m_pAttachedCloningArea );
  145. m_pAttachedCloningArea = NULL;
  146. }
  147. BaseClass::UpdateOnRemove();
  148. }
  149. void CProp_Portal::Precache( void )
  150. {
  151. PrecacheScriptSound( "Portal.ambient_loop" );
  152. PrecacheScriptSound( "Portal.open_blue" );
  153. PrecacheScriptSound( "Portal.open_red" );
  154. PrecacheScriptSound( "Portal.close_blue" );
  155. PrecacheScriptSound( "Portal.close_red" );
  156. PrecacheScriptSound( "Portal.fizzle_moved" );
  157. PrecacheScriptSound( "Portal.fizzle_invalid_surface" );
  158. PrecacheModel( "models/portals/portal1.mdl" );
  159. PrecacheModel( "models/portals/portal2.mdl" );
  160. PrecacheParticleSystem( "portal_1_particles" );
  161. PrecacheParticleSystem( "portal_2_particles" );
  162. PrecacheParticleSystem( "portal_1_edge" );
  163. PrecacheParticleSystem( "portal_2_edge" );
  164. PrecacheParticleSystem( "portal_1_nofit" );
  165. PrecacheParticleSystem( "portal_2_nofit" );
  166. PrecacheParticleSystem( "portal_1_overlap" );
  167. PrecacheParticleSystem( "portal_2_overlap" );
  168. PrecacheParticleSystem( "portal_1_badvolume" );
  169. PrecacheParticleSystem( "portal_2_badvolume" );
  170. PrecacheParticleSystem( "portal_1_badsurface" );
  171. PrecacheParticleSystem( "portal_2_badsurface" );
  172. PrecacheParticleSystem( "portal_1_close" );
  173. PrecacheParticleSystem( "portal_2_close" );
  174. PrecacheParticleSystem( "portal_1_cleanser" );
  175. PrecacheParticleSystem( "portal_2_cleanser" );
  176. PrecacheParticleSystem( "portal_1_near" );
  177. PrecacheParticleSystem( "portal_2_near" );
  178. PrecacheParticleSystem( "portal_1_success" );
  179. PrecacheParticleSystem( "portal_2_success" );
  180. BaseClass::Precache();
  181. }
  182. void CProp_Portal::CreateSounds()
  183. {
  184. if (!m_pAmbientSound)
  185. {
  186. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  187. CPASAttenuationFilter filter( this );
  188. m_pAmbientSound = controller.SoundCreate( filter, entindex(), "Portal.ambient_loop" );
  189. controller.Play( m_pAmbientSound, 0, 100 );
  190. }
  191. }
  192. void CProp_Portal::StopLoopingSounds()
  193. {
  194. if ( m_pAmbientSound )
  195. {
  196. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  197. controller.SoundDestroy( m_pAmbientSound );
  198. m_pAmbientSound = NULL;
  199. }
  200. BaseClass::StopLoopingSounds();
  201. }
  202. void CProp_Portal::Spawn( void )
  203. {
  204. Precache();
  205. Assert( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) == -1 );
  206. s_PortalLinkageGroups[m_iLinkageGroupID].AddToTail( this );
  207. m_matrixThisToLinked.Identity(); //don't accidentally teleport objects to zero space
  208. AddEffects( EF_NORECEIVESHADOW | EF_NOSHADOW );
  209. SetSolid( SOLID_OBB );
  210. SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID | FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
  211. SetMoveType( MOVETYPE_NONE );
  212. SetCollisionGroup( COLLISION_GROUP_PLAYER );
  213. //VPhysicsInitNormal( SOLID_VPHYSICS, FSOLID_TRIGGER, false );
  214. //CreateVPhysics();
  215. ResetModel();
  216. SetSize( CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs );
  217. UpdateCorners();
  218. BaseClass::Spawn();
  219. m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
  220. }
  221. void CProp_Portal::OnRestore()
  222. {
  223. UpdateCorners();
  224. Assert( m_pAttachedCloningArea == NULL );
  225. m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
  226. BaseClass::OnRestore();
  227. if ( m_bActivated )
  228. {
  229. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_particles" ) : ( "portal_1_particles" ) ), PATTACH_POINT_FOLLOW, this, "particles_2", true );
  230. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_edge" ) : ( "portal_1_edge" ) ), PATTACH_POINT_FOLLOW, this, "particlespin" );
  231. }
  232. }
  233. void DumpActiveCollision( const CPortalSimulator *pPortalSimulator, const char *szFileName );
  234. void PortalSimulatorDumps_DumpCollideToGlView( CPhysCollide *pCollide, const Vector &origin, const QAngle &angles, float fColorScale, const char *pFilename );
  235. bool CProp_Portal::TestCollision( const Ray_t &ray, unsigned int fContentsMask, trace_t& tr )
  236. {
  237. physcollision->TraceBox( ray, MASK_ALL, NULL, m_pCollisionShape, GetAbsOrigin(), GetAbsAngles(), &tr );
  238. return tr.DidHit();
  239. }
  240. //-----------------------------------------------------------------------------
  241. // Purpose: Runs when a fired portal shot reaches it's destination wall. Detects current placement valididty state.
  242. //-----------------------------------------------------------------------------
  243. void CProp_Portal::DelayedPlacementThink( void )
  244. {
  245. Vector vOldOrigin = GetLocalOrigin();
  246. QAngle qOldAngles = GetLocalAngles();
  247. Vector vForward;
  248. AngleVectors( m_qDelayedAngles, &vForward );
  249. // Check if something made the spot invalid mid flight
  250. // Bad surface and near fizzle effects take priority
  251. if ( m_iDelayedFailure != PORTAL_FIZZLE_BAD_SURFACE && m_iDelayedFailure != PORTAL_FIZZLE_NEAR_BLUE && m_iDelayedFailure != PORTAL_FIZZLE_NEAR_RED )
  252. {
  253. if ( IsPortalOverlappingOtherPortals( this, m_vDelayedPosition, m_qDelayedAngles ) )
  254. {
  255. m_iDelayedFailure = PORTAL_FIZZLE_OVERLAPPED_LINKED;
  256. }
  257. else if ( IsPortalIntersectingNoPortalVolume( m_vDelayedPosition, m_qDelayedAngles, vForward ) )
  258. {
  259. m_iDelayedFailure = PORTAL_FIZZLE_BAD_VOLUME;
  260. }
  261. }
  262. if ( sv_portal_placement_never_fail.GetBool() )
  263. {
  264. m_iDelayedFailure = PORTAL_FIZZLE_SUCCESS;
  265. }
  266. DoFizzleEffect( m_iDelayedFailure );
  267. if ( m_iDelayedFailure != PORTAL_FIZZLE_SUCCESS )
  268. {
  269. // It didn't successfully place
  270. return;
  271. }
  272. // Do effects at old location if it was active
  273. if ( m_bActivated )
  274. {
  275. DoFizzleEffect( PORTAL_FIZZLE_CLOSE, false );
  276. }
  277. CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
  278. if( pPortalGun )
  279. {
  280. CPortal_Player *pFiringPlayer = dynamic_cast<CPortal_Player *>( pPortalGun->GetOwner() );
  281. if( pFiringPlayer )
  282. {
  283. pFiringPlayer->IncrementPortalsPlaced();
  284. // Placement successful, fire the output
  285. m_OnPlacedSuccessfully.FireOutput( pPortalGun, this );
  286. }
  287. }
  288. // Move to new location
  289. NewLocation( m_vDelayedPosition, m_qDelayedAngles );
  290. SetContextThink( &CProp_Portal::TestRestingSurfaceThink, gpGlobals->curtime + 0.1f, s_pTestRestingSurfaceContext );
  291. }
  292. //-----------------------------------------------------------------------------
  293. // Purpose: When placed on a surface that could potentially go away (anything but world geo), we test for that condition and fizzle
  294. //-----------------------------------------------------------------------------
  295. void CProp_Portal::TestRestingSurfaceThink( void )
  296. {
  297. // Make sure there's still a surface behind the portal
  298. Vector vOrigin = GetAbsOrigin();
  299. Vector vForward, vRight, vUp;
  300. GetVectors( &vForward, &vRight, &vUp );
  301. trace_t tr;
  302. CTraceFilterSimpleClassnameList baseFilter( NULL, COLLISION_GROUP_NONE );
  303. UTIL_Portal_Trace_Filter( &baseFilter );
  304. baseFilter.AddClassnameToIgnore( "prop_portal" );
  305. CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
  306. int iCornersOnVolatileSurface = 0;
  307. // Check corners
  308. for ( int iCorner = 0; iCorner < 4; ++iCorner )
  309. {
  310. Vector vCorner = vOrigin;
  311. if ( iCorner % 2 == 0 )
  312. vCorner += vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS * 1.1f );
  313. else
  314. vCorner += -vRight * ( PORTAL_HALF_WIDTH - PORTAL_BUMP_FORGIVENESS * 1.1f );
  315. if ( iCorner < 2 )
  316. vCorner += vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS * 1.1f );
  317. else
  318. vCorner += -vUp * ( PORTAL_HALF_HEIGHT - PORTAL_BUMP_FORGIVENESS * 1.1f );
  319. Ray_t ray;
  320. ray.Init( vCorner, vCorner - vForward );
  321. enginetrace->TraceRay( ray, MASK_SOLID_BRUSHONLY, &traceFilterPortalShot, &tr );
  322. // 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).
  323. 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" ) ) ) )
  324. {
  325. DevMsg( "Surface removed from behind portal.\n" );
  326. Fizzle();
  327. SetContextThink( NULL, TICK_NEVER_THINK, s_pTestRestingSurfaceContext );
  328. break;
  329. }
  330. if ( !tr.DidHitWorld() )
  331. {
  332. iCornersOnVolatileSurface++;
  333. }
  334. }
  335. // Still on a movable or deletable surface
  336. if ( iCornersOnVolatileSurface > 0 )
  337. {
  338. SetContextThink ( &CProp_Portal::TestRestingSurfaceThink, gpGlobals->curtime + 0.1f, s_pTestRestingSurfaceContext );
  339. }
  340. else
  341. {
  342. // All corners on world, we don't need to test
  343. SetContextThink( NULL, TICK_NEVER_THINK, s_pTestRestingSurfaceContext );
  344. }
  345. }
  346. bool CProp_Portal::IsActivedAndLinked( void ) const
  347. {
  348. return ( m_bActivated && m_hLinkedPortal.Get() != NULL );
  349. }
  350. void CProp_Portal::ResetModel( void )
  351. {
  352. if( !m_bIsPortal2 )
  353. SetModel( "models/portals/portal1.mdl" );
  354. else
  355. SetModel( "models/portals/portal2.mdl" );
  356. SetSize( CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs );
  357. SetSolid( SOLID_OBB );
  358. SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID | FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
  359. }
  360. void CProp_Portal::DoFizzleEffect( int iEffect, bool bDelayedPos /*= true*/ )
  361. {
  362. m_vAudioOrigin = ( ( bDelayedPos ) ? ( m_vDelayedPosition ) : ( GetAbsOrigin() ) );
  363. CEffectData fxData;
  364. fxData.m_vAngles = ( ( bDelayedPos ) ? ( m_qDelayedAngles ) : ( GetAbsAngles() ) );
  365. Vector vForward, vUp;
  366. AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
  367. fxData.m_vOrigin = m_vAudioOrigin + vForward * 1.0f;
  368. fxData.m_nColor = ( ( m_bIsPortal2 ) ? ( 1 ) : ( 0 ) );
  369. EmitSound_t ep;
  370. CPASAttenuationFilter filter( m_vDelayedPosition );
  371. ep.m_nChannel = CHAN_STATIC;
  372. ep.m_flVolume = 1.0f;
  373. ep.m_pOrigin = &m_vAudioOrigin;
  374. // Rumble effects on the firing player (if one exists)
  375. CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
  376. if ( pPortalGun && (iEffect != PORTAL_FIZZLE_CLOSE )
  377. && (iEffect != PORTAL_FIZZLE_SUCCESS )
  378. && (iEffect != PORTAL_FIZZLE_NONE ) )
  379. {
  380. CBasePlayer* pPlayer = (CBasePlayer*)pPortalGun->GetOwner();
  381. if ( pPlayer )
  382. {
  383. pPlayer->RumbleEffect( RUMBLE_PORTAL_PLACEMENT_FAILURE, 0, RUMBLE_FLAGS_NONE );
  384. }
  385. }
  386. // Pick a fizzle effect
  387. switch ( iEffect )
  388. {
  389. case PORTAL_FIZZLE_CANT_FIT:
  390. //DispatchEffect( "PortalFizzleCantFit", fxData );
  391. //ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  392. VectorAngles( vUp, vForward, fxData.m_vAngles );
  393. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_nofit" ) : ( "portal_1_nofit" ) ), fxData.m_vOrigin, fxData.m_vAngles, this );
  394. break;
  395. case PORTAL_FIZZLE_OVERLAPPED_LINKED:
  396. {
  397. /*CProp_Portal *pLinkedPortal = m_hLinkedPortal;
  398. if ( pLinkedPortal )
  399. {
  400. Vector vLinkedForward;
  401. pLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
  402. fxData.m_vStart = pLink3edPortal->GetAbsOrigin() + vLinkedForward * 5.0f;
  403. }*/
  404. //DispatchEffect( "PortalFizzleOverlappedLinked", fxData );
  405. VectorAngles( vUp, vForward, fxData.m_vAngles );
  406. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_overlap" ) : ( "portal_1_overlap" ) ), fxData.m_vOrigin, fxData.m_vAngles, this );
  407. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  408. break;
  409. }
  410. case PORTAL_FIZZLE_BAD_VOLUME:
  411. //DispatchEffect( "PortalFizzleBadVolume", fxData );
  412. VectorAngles( vUp, vForward, fxData.m_vAngles );
  413. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_badvolume" ) : ( "portal_1_badvolume" ) ), fxData.m_vOrigin, fxData.m_vAngles );
  414. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  415. break;
  416. case PORTAL_FIZZLE_BAD_SURFACE:
  417. //DispatchEffect( "PortalFizzleBadSurface", fxData );
  418. VectorAngles( vUp, vForward, fxData.m_vAngles );
  419. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_badsurface" ) : ( "portal_1_badsurface" ) ), fxData.m_vOrigin, fxData.m_vAngles );
  420. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  421. break;
  422. case PORTAL_FIZZLE_KILLED:
  423. //DispatchEffect( "PortalFizzleKilled", fxData );
  424. VectorAngles( vUp, vForward, fxData.m_vAngles );
  425. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_close" ) : ( "portal_1_close" ) ), fxData.m_vOrigin, fxData.m_vAngles );
  426. ep.m_pSoundName = "Portal.fizzle_moved";
  427. break;
  428. case PORTAL_FIZZLE_CLEANSER:
  429. //DispatchEffect( "PortalFizzleCleanser", fxData );
  430. VectorAngles( vUp, vForward, fxData.m_vAngles );
  431. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_cleanser" ) : ( "portal_1_cleanser" ) ), fxData.m_vOrigin, fxData.m_vAngles, this );
  432. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  433. break;
  434. case PORTAL_FIZZLE_CLOSE:
  435. //DispatchEffect( "PortalFizzleKilled", fxData );
  436. VectorAngles( vUp, vForward, fxData.m_vAngles );
  437. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_close" ) : ( "portal_1_close" ) ), fxData.m_vOrigin, fxData.m_vAngles );
  438. ep.m_pSoundName = ( ( m_bIsPortal2 ) ? ( "Portal.close_red" ) : ( "Portal.close_blue" ) );
  439. break;
  440. case PORTAL_FIZZLE_NEAR_BLUE:
  441. {
  442. if ( !m_bIsPortal2 )
  443. {
  444. Vector vLinkedForward;
  445. m_hLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
  446. fxData.m_vOrigin = m_hLinkedPortal->GetAbsOrigin() + vLinkedForward * 16.0f;
  447. fxData.m_vAngles = m_hLinkedPortal->GetAbsAngles();
  448. }
  449. else
  450. {
  451. GetVectors( &vForward, NULL, NULL );
  452. fxData.m_vOrigin = GetAbsOrigin() + vForward * 16.0f;
  453. fxData.m_vAngles = GetAbsAngles();
  454. }
  455. //DispatchEffect( "PortalFizzleNear", fxData );
  456. AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
  457. VectorAngles( vUp, vForward, fxData.m_vAngles );
  458. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_near" ) : ( "portal_1_near" ) ), fxData.m_vOrigin, fxData.m_vAngles );
  459. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  460. break;
  461. }
  462. case PORTAL_FIZZLE_NEAR_RED:
  463. {
  464. if ( m_bIsPortal2 )
  465. {
  466. Vector vLinkedForward;
  467. m_hLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
  468. fxData.m_vOrigin = m_hLinkedPortal->GetAbsOrigin() + vLinkedForward * 16.0f;
  469. fxData.m_vAngles = m_hLinkedPortal->GetAbsAngles();
  470. }
  471. else
  472. {
  473. GetVectors( &vForward, NULL, NULL );
  474. fxData.m_vOrigin = GetAbsOrigin() + vForward * 16.0f;
  475. fxData.m_vAngles = GetAbsAngles();
  476. }
  477. //DispatchEffect( "PortalFizzleNear", fxData );
  478. AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
  479. VectorAngles( vUp, vForward, fxData.m_vAngles );
  480. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_near" ) : ( "portal_1_near" ) ), fxData.m_vOrigin, fxData.m_vAngles );
  481. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  482. break;
  483. }
  484. case PORTAL_FIZZLE_SUCCESS:
  485. VectorAngles( vUp, vForward, fxData.m_vAngles );
  486. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_success" ) : ( "portal_1_success" ) ), fxData.m_vOrigin, fxData.m_vAngles );
  487. // Don't make a sound!
  488. return;
  489. case PORTAL_FIZZLE_NONE:
  490. // Don't do anything!
  491. return;
  492. }
  493. EmitSound( filter, SOUND_FROM_WORLD, ep );
  494. }
  495. //-----------------------------------------------------------------------------
  496. // Purpose: Fizzle the portal
  497. //-----------------------------------------------------------------------------
  498. void CProp_Portal::FizzleThink( void )
  499. {
  500. CProp_Portal *pRemotePortal = m_hLinkedPortal;
  501. RemovePortalMicAndSpeaker();
  502. if ( m_pAmbientSound )
  503. {
  504. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  505. controller.SoundChangeVolume( m_pAmbientSound, 0.0, 0.0 );
  506. }
  507. StopParticleEffects( this );
  508. m_bActivated = false;
  509. m_hLinkedPortal = NULL;
  510. m_PortalSimulator.DetachFromLinked();
  511. m_PortalSimulator.ReleaseAllEntityOwnership();
  512. if( pRemotePortal )
  513. {
  514. //pRemotePortal->m_hLinkedPortal = NULL;
  515. pRemotePortal->UpdatePortalLinkage();
  516. }
  517. SetContextThink( NULL, TICK_NEVER_THINK, s_pFizzleThink );
  518. }
  519. //-----------------------------------------------------------------------------
  520. // Purpose: Portal will fizzle next time we get to think
  521. //-----------------------------------------------------------------------------
  522. void CProp_Portal::Fizzle( void )
  523. {
  524. SetContextThink( &CProp_Portal::FizzleThink, gpGlobals->curtime, s_pFizzleThink );
  525. }
  526. //-----------------------------------------------------------------------------
  527. // Purpose: Removes the portal microphone and speakers. This is done in two places
  528. // (fizzle and UpdateOnRemove) so the code is consolidated here.
  529. // Input : -
  530. //-----------------------------------------------------------------------------
  531. void CProp_Portal::RemovePortalMicAndSpeaker()
  532. {
  533. // Shut down microphone/speaker if they exist
  534. if ( m_hMicrophone )
  535. {
  536. CEnvMicrophone *pMicrophone = (CEnvMicrophone*)(m_hMicrophone.Get());
  537. if ( pMicrophone )
  538. {
  539. inputdata_t in;
  540. pMicrophone->InputDisable( in );
  541. UTIL_Remove( pMicrophone );
  542. }
  543. m_hMicrophone = 0;
  544. }
  545. if ( m_hSpeaker )
  546. {
  547. CSpeaker *pSpeaker = (CSpeaker *)(m_hSpeaker.Get());
  548. if ( pSpeaker )
  549. {
  550. // Remove the remote portal's microphone, as it references the speaker we're about to remove.
  551. if ( m_hLinkedPortal.Get() )
  552. {
  553. CProp_Portal* pRemotePortal = m_hLinkedPortal.Get();
  554. if ( pRemotePortal->m_hMicrophone )
  555. {
  556. inputdata_t inputdata;
  557. inputdata.pActivator = this;
  558. inputdata.pCaller = this;
  559. CEnvMicrophone* pRemotePortalMic = dynamic_cast<CEnvMicrophone*>(pRemotePortal->m_hMicrophone.Get());
  560. if ( pRemotePortalMic )
  561. {
  562. pRemotePortalMic->Remove();
  563. }
  564. }
  565. }
  566. inputdata_t in;
  567. pSpeaker->InputTurnOff( in );
  568. UTIL_Remove( pSpeaker );
  569. }
  570. m_hSpeaker = 0;
  571. }
  572. }
  573. void CProp_Portal::PunchPenetratingPlayer( CBaseEntity *pPlayer )
  574. {
  575. if( m_PortalSimulator.IsReadyToSimulate() )
  576. {
  577. ICollideable *pCollideable = pPlayer->GetCollideable();
  578. if ( pCollideable )
  579. {
  580. Vector vMin, vMax;
  581. pCollideable->WorldSpaceSurroundingBounds( &vMin, &vMax );
  582. if ( UTIL_IsBoxIntersectingPortal( ( vMin + vMax ) / 2.0f, ( vMax - vMin ) / 2.0f, this ) )
  583. {
  584. Vector vForward;
  585. GetVectors( &vForward, 0, 0 );
  586. vForward *= 100.0f;
  587. pPlayer->VelocityPunch( vForward );
  588. }
  589. }
  590. }
  591. }
  592. void CProp_Portal::PunchAllPenetratingPlayers( void )
  593. {
  594. for( int i = 1; i <= gpGlobals->maxClients; ++i )
  595. {
  596. CBasePlayer *pPlayer = UTIL_PlayerByIndex( i );
  597. if( pPlayer )
  598. PunchPenetratingPlayer( pPlayer );
  599. }
  600. }
  601. void CProp_Portal::Activate( void )
  602. {
  603. if( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) == -1 )
  604. s_PortalLinkageGroups[m_iLinkageGroupID].AddToTail( this );
  605. if( m_pAttachedCloningArea == NULL )
  606. m_pAttachedCloningArea = CPhysicsCloneArea::CreatePhysicsCloneArea( this );
  607. UpdatePortalTeleportMatrix();
  608. UpdatePortalLinkage();
  609. BaseClass::Activate();
  610. CreateSounds();
  611. AddEffects( EF_NOSHADOW | EF_NORECEIVESHADOW );
  612. if( m_bActivated && (m_hLinkedPortal.Get() != NULL) )
  613. {
  614. Vector ptCenter = GetAbsOrigin();
  615. QAngle qAngles = GetAbsAngles();
  616. m_PortalSimulator.MoveTo( ptCenter, qAngles );
  617. //resimulate everything we're touching
  618. touchlink_t *root = ( touchlink_t * )GetDataObject( TOUCHLINK );
  619. if( root )
  620. {
  621. for( touchlink_t *link = root->nextLink; link != root; link = link->nextLink )
  622. {
  623. CBaseEntity *pOther = link->entityTouched;
  624. if( CProp_Portal_Shared::IsEntityTeleportable( pOther ) )
  625. {
  626. CCollisionProperty *pOtherCollision = pOther->CollisionProp();
  627. Vector vWorldMins, vWorldMaxs;
  628. pOtherCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
  629. Vector ptOtherCenter = (vWorldMins + vWorldMaxs) / 2.0f;
  630. if( m_plane_Origin.normal.Dot( ptOtherCenter ) > m_plane_Origin.dist )
  631. {
  632. //we should be interacting with this object, add it to our environment
  633. if( SharedEnvironmentCheck( pOther ) )
  634. {
  635. Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
  636. (m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
  637. CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
  638. if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
  639. pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
  640. m_PortalSimulator.TakeOwnershipOfEntity( pOther );
  641. }
  642. }
  643. }
  644. }
  645. }
  646. }
  647. }
  648. bool CProp_Portal::ShouldTeleportTouchingEntity( CBaseEntity *pOther )
  649. {
  650. if( !m_PortalSimulator.OwnsEntity( pOther ) ) //can't teleport an entity we don't own
  651. {
  652. #if !defined ( DISABLE_DEBUG_HISTORY )
  653. if ( !IsMarkedForDeletion() )
  654. {
  655. ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i not teleporting %s because it's not simulated by this portal.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() ) );
  656. }
  657. #endif
  658. if ( sv_portal_debug_touch.GetBool() )
  659. {
  660. Msg( "Portal %i not teleporting %s because it's not simulated by this portal. : %f \n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName(), gpGlobals->curtime );
  661. }
  662. return false;
  663. }
  664. if( !CProp_Portal_Shared::IsEntityTeleportable( pOther ) )
  665. return false;
  666. if( m_hLinkedPortal.Get() == NULL )
  667. {
  668. #if !defined ( DISABLE_DEBUG_HISTORY )
  669. if ( !IsMarkedForDeletion() )
  670. {
  671. ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i not teleporting %s because it has no linked partner portal.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() ) );
  672. }
  673. #endif
  674. if ( sv_portal_debug_touch.GetBool() )
  675. {
  676. Msg( "Portal %i not teleporting %s because it has no linked partner portal.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() );
  677. }
  678. return false;
  679. }
  680. //Vector ptOtherOrigin = pOther->GetAbsOrigin();
  681. Vector ptOtherCenter = pOther->WorldSpaceCenter();
  682. IPhysicsObject *pOtherPhysObject = pOther->VPhysicsGetObject();
  683. Vector vOtherVelocity;
  684. //grab current velocity
  685. {
  686. if( sv_portal_new_velocity_check.GetBool() )
  687. {
  688. //we're assuming that physics velocity is the most reliable of all if the convar is true
  689. if( pOtherPhysObject )
  690. {
  691. //pOtherPhysObject->GetImplicitVelocity( &vOtherVelocity, NULL );
  692. pOtherPhysObject->GetVelocity( &vOtherVelocity, NULL );
  693. if( vOtherVelocity == vec3_origin )
  694. {
  695. pOther->GetVelocity( &vOtherVelocity );
  696. }
  697. }
  698. else
  699. {
  700. pOther->GetVelocity( &vOtherVelocity );
  701. }
  702. }
  703. else
  704. {
  705. //old style of velocity grabbing, which uses implicit velocity as a last resort
  706. if( pOther->GetMoveType() == MOVETYPE_VPHYSICS )
  707. {
  708. if( pOtherPhysObject && (pOtherPhysObject->GetShadowController() == NULL) )
  709. pOtherPhysObject->GetVelocity( &vOtherVelocity, NULL );
  710. else
  711. pOther->GetVelocity( &vOtherVelocity );
  712. }
  713. else
  714. {
  715. pOther->GetVelocity( &vOtherVelocity );
  716. }
  717. if( vOtherVelocity == vec3_origin )
  718. {
  719. // Recorded velocity is sometimes zero under pushed or teleported movement, or after position correction.
  720. // In these circumstances, we want implicit velocity ((last pos - this pos) / timestep )
  721. if ( pOtherPhysObject )
  722. {
  723. Vector vOtherImplicitVelocity;
  724. pOtherPhysObject->GetImplicitVelocity( &vOtherImplicitVelocity, NULL );
  725. vOtherVelocity += vOtherImplicitVelocity;
  726. }
  727. }
  728. }
  729. }
  730. // Test for entity's center being past portal plane
  731. if(m_PortalSimulator.m_DataAccess.Placement.PortalPlane.m_Normal.Dot( ptOtherCenter ) < m_PortalSimulator.m_DataAccess.Placement.PortalPlane.m_Dist)
  732. {
  733. //entity wants to go further into the plane
  734. if( m_PortalSimulator.EntityIsInPortalHole( pOther ) )
  735. {
  736. #ifdef _DEBUG
  737. static int iAntiRecurse = 0;
  738. if( pOther->IsPlayer() && (iAntiRecurse == 0) )
  739. {
  740. ++iAntiRecurse;
  741. ShouldTeleportTouchingEntity( pOther ); //do it again for debugging
  742. --iAntiRecurse;
  743. }
  744. #endif
  745. return true;
  746. }
  747. else
  748. {
  749. #if !defined ( DISABLE_DEBUG_HISTORY )
  750. if ( !IsMarkedForDeletion() )
  751. {
  752. ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i not teleporting %s because it was not in the portal hole.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() ) );
  753. }
  754. #endif
  755. if ( sv_portal_debug_touch.GetBool() )
  756. {
  757. Msg( "Portal %i not teleporting %s because it was not in the portal hole.\n", ((m_bIsPortal2)?(2):(1)), pOther->GetDebugName() );
  758. }
  759. }
  760. }
  761. return false;
  762. }
  763. void CProp_Portal::TeleportTouchingEntity( CBaseEntity *pOther )
  764. {
  765. if ( GetPortalCallQueue() )
  766. {
  767. GetPortalCallQueue()->QueueCall( this, &CProp_Portal::TeleportTouchingEntity, pOther );
  768. return;
  769. }
  770. Assert( m_hLinkedPortal.Get() != NULL );
  771. Vector ptOtherOrigin = pOther->GetAbsOrigin();
  772. Vector ptOtherCenter;
  773. bool bPlayer = pOther->IsPlayer();
  774. QAngle qPlayerEyeAngles;
  775. CPortal_Player *pOtherAsPlayer;
  776. if( bPlayer )
  777. {
  778. //NDebugOverlay::EntityBounds( pOther, 255, 0, 0, 128, 60.0f );
  779. pOtherAsPlayer = (CPortal_Player *)pOther;
  780. qPlayerEyeAngles = pOtherAsPlayer->pl.v_angle;
  781. }
  782. else
  783. {
  784. pOtherAsPlayer = NULL;
  785. }
  786. ptOtherCenter = pOther->WorldSpaceCenter();
  787. bool bNonPhysical = false; //special case handling for non-physical objects such as the energy ball and player
  788. QAngle qOtherAngles;
  789. Vector vOtherVelocity;
  790. //grab current velocity
  791. {
  792. IPhysicsObject *pOtherPhysObject = pOther->VPhysicsGetObject();
  793. if( pOther->GetMoveType() == MOVETYPE_VPHYSICS )
  794. {
  795. if( pOtherPhysObject && (pOtherPhysObject->GetShadowController() == NULL) )
  796. pOtherPhysObject->GetVelocity( &vOtherVelocity, NULL );
  797. else
  798. pOther->GetVelocity( &vOtherVelocity );
  799. }
  800. else if ( bPlayer && pOther->VPhysicsGetObject() )
  801. {
  802. pOther->VPhysicsGetObject()->GetVelocity( &vOtherVelocity, NULL );
  803. if ( vOtherVelocity == vec3_origin )
  804. {
  805. vOtherVelocity = pOther->GetAbsVelocity();
  806. }
  807. }
  808. else
  809. {
  810. pOther->GetVelocity( &vOtherVelocity );
  811. }
  812. if( vOtherVelocity == vec3_origin )
  813. {
  814. // Recorded velocity is sometimes zero under pushed or teleported movement, or after position correction.
  815. // In these circumstances, we want implicit velocity ((last pos - this pos) / timestep )
  816. if ( pOtherPhysObject )
  817. {
  818. Vector vOtherImplicitVelocity;
  819. pOtherPhysObject->GetImplicitVelocity( &vOtherImplicitVelocity, NULL );
  820. vOtherVelocity += vOtherImplicitVelocity;
  821. }
  822. }
  823. }
  824. const PS_InternalData_t &RemotePortalDataAccess = m_hLinkedPortal->m_PortalSimulator.m_DataAccess;
  825. const PS_InternalData_t &LocalPortalDataAccess = m_PortalSimulator.m_DataAccess;
  826. if( bPlayer )
  827. {
  828. qOtherAngles = pOtherAsPlayer->EyeAngles();
  829. pOtherAsPlayer->m_qPrePortalledViewAngles = qOtherAngles;
  830. pOtherAsPlayer->m_bFixEyeAnglesFromPortalling = true;
  831. pOtherAsPlayer->m_matLastPortalled = m_matrixThisToLinked;
  832. bNonPhysical = true;
  833. //if( (fabs( RemotePortalDataAccess.Placement.vForward.z ) + fabs( LocalPortalDataAccess.Placement.vForward.z )) > 0.7071f ) //some combination of floor/ceiling
  834. if( fabs( LocalPortalDataAccess.Placement.vForward.z ) > 0.0f )
  835. {
  836. //we may have to compensate for the fact that AABB's don't rotate ever
  837. float fAbsLocalZ = fabs( LocalPortalDataAccess.Placement.vForward.z );
  838. float fAbsRemoteZ = fabs( RemotePortalDataAccess.Placement.vForward.z );
  839. if( (fabs(fAbsLocalZ - 1.0f) < 0.01f) &&
  840. (fabs(fAbsRemoteZ - 1.0f) < 0.01f) )
  841. //(fabs( LocalPortalDataAccess.Placement.vForward.z + RemotePortalDataAccess.Placement.vForward.z ) < 0.01f) )
  842. {
  843. //portals are both aligned on the z axis, no need to shrink the player
  844. }
  845. else
  846. {
  847. //curl the player up into a little ball
  848. pOtherAsPlayer->SetGroundEntity( NULL );
  849. if( !pOtherAsPlayer->IsDucked() )
  850. {
  851. pOtherAsPlayer->ForceDuckThisFrame();
  852. pOtherAsPlayer->m_Local.m_bInDuckJump = true;
  853. if( LocalPortalDataAccess.Placement.vForward.z > 0.0f )
  854. ptOtherCenter.z -= 16.0f; //portal facing up, shrink downwards
  855. else
  856. ptOtherCenter.z += 16.0f; //portal facing down, shrink upwards
  857. }
  858. }
  859. }
  860. }
  861. else
  862. {
  863. qOtherAngles = pOther->GetAbsAngles();
  864. bNonPhysical = FClassnameIs( pOther, "prop_energy_ball" );
  865. }
  866. Vector ptNewOrigin;
  867. QAngle qNewAngles;
  868. Vector vNewVelocity;
  869. //apply transforms to relevant variables (applied to the entity later)
  870. {
  871. if( bPlayer )
  872. {
  873. ptNewOrigin = m_matrixThisToLinked * ptOtherCenter;
  874. ptNewOrigin += ptOtherOrigin - ptOtherCenter;
  875. }
  876. else
  877. {
  878. ptNewOrigin = m_matrixThisToLinked * ptOtherOrigin;
  879. }
  880. // Reorient object angles, originally we did a transformation on the angles, but that doesn't quite work right for gimbal lock cases
  881. qNewAngles = TransformAnglesToWorldSpace( qOtherAngles, m_matrixThisToLinked.As3x4() );
  882. qNewAngles.x = AngleNormalizePositive( qNewAngles.x );
  883. qNewAngles.y = AngleNormalizePositive( qNewAngles.y );
  884. qNewAngles.z = AngleNormalizePositive( qNewAngles.z );
  885. // Reorient the velocity
  886. vNewVelocity = m_matrixThisToLinked.ApplyRotation( vOtherVelocity );
  887. }
  888. //help camera reorientation for the player
  889. if( bPlayer )
  890. {
  891. Vector vPlayerForward;
  892. AngleVectors( qOtherAngles, &vPlayerForward, NULL, NULL );
  893. float fPlayerForwardZ = vPlayerForward.z;
  894. vPlayerForward.z = 0.0f;
  895. float fForwardLength = vPlayerForward.Length();
  896. if ( fForwardLength > 0.0f )
  897. {
  898. VectorNormalize( vPlayerForward );
  899. }
  900. float fPlayerFaceDotPortalFace = LocalPortalDataAccess.Placement.vForward.Dot( vPlayerForward );
  901. float fPlayerFaceDotPortalUp = LocalPortalDataAccess.Placement.vUp.Dot( vPlayerForward );
  902. CBaseEntity *pHeldEntity = GetPlayerHeldEntity( pOtherAsPlayer );
  903. // Sometimes reorienting by pitch is more desirable than by roll depending on the portals' orientations and the relative player facing direction
  904. if ( pHeldEntity ) // never pitch reorient while holding an object
  905. {
  906. pOtherAsPlayer->m_bPitchReorientation = false;
  907. }
  908. else if ( LocalPortalDataAccess.Placement.vUp.z > 0.99f && // entering wall portal
  909. ( fForwardLength == 0.0f || // facing strait up or down
  910. fPlayerFaceDotPortalFace > 0.5f || // facing mostly away from portal
  911. fPlayerFaceDotPortalFace < -0.5f ) // facing mostly toward portal
  912. )
  913. {
  914. pOtherAsPlayer->m_bPitchReorientation = true;
  915. }
  916. else if ( ( LocalPortalDataAccess.Placement.vForward.z > 0.99f || LocalPortalDataAccess.Placement.vForward.z < -0.99f ) && // entering floor or ceiling portal
  917. ( RemotePortalDataAccess.Placement.vForward.z > 0.99f || RemotePortalDataAccess.Placement.vForward.z < -0.99f ) && // exiting floor or ceiling portal
  918. ( fPlayerForwardZ < -0.5f || fPlayerForwardZ > 0.5f ) // facing mustly up or down
  919. )
  920. {
  921. pOtherAsPlayer->m_bPitchReorientation = true;
  922. }
  923. else if ( ( RemotePortalDataAccess.Placement.vForward.z > 0.75f && RemotePortalDataAccess.Placement.vForward.z <= 0.99f ) && // exiting wedge portal
  924. ( fPlayerFaceDotPortalUp > 0.0f ) // facing toward the top of the portal
  925. )
  926. {
  927. pOtherAsPlayer->m_bPitchReorientation = true;
  928. }
  929. else
  930. {
  931. pOtherAsPlayer->m_bPitchReorientation = false;
  932. }
  933. }
  934. //velocity hacks
  935. {
  936. //minimum floor exit velocity if both portals are on the floor or the player is coming out of the floor
  937. if( RemotePortalDataAccess.Placement.vForward.z > 0.7071f )
  938. {
  939. if ( bPlayer )
  940. {
  941. if( vNewVelocity.z < MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY_PLAYER )
  942. vNewVelocity.z = MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY_PLAYER;
  943. }
  944. else
  945. {
  946. if( LocalPortalDataAccess.Placement.vForward.z > 0.7071f )
  947. {
  948. if( vNewVelocity.z < MINIMUM_FLOOR_TO_FLOOR_PORTAL_EXIT_VELOCITY )
  949. vNewVelocity.z = MINIMUM_FLOOR_TO_FLOOR_PORTAL_EXIT_VELOCITY;
  950. }
  951. else
  952. {
  953. if( vNewVelocity.z < MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY )
  954. vNewVelocity.z = MINIMUM_FLOOR_PORTAL_EXIT_VELOCITY;
  955. }
  956. }
  957. }
  958. if ( vNewVelocity.LengthSqr() > (MAXIMUM_PORTAL_EXIT_VELOCITY * MAXIMUM_PORTAL_EXIT_VELOCITY) )
  959. vNewVelocity *= (MAXIMUM_PORTAL_EXIT_VELOCITY / vNewVelocity.Length());
  960. }
  961. //untouch the portal(s), will force a touch on destination after the teleport
  962. {
  963. m_PortalSimulator.ReleaseOwnershipOfEntity( pOther, true );
  964. this->PhysicsNotifyOtherOfUntouch( this, pOther );
  965. pOther->PhysicsNotifyOtherOfUntouch( pOther, this );
  966. m_hLinkedPortal->m_PortalSimulator.TakeOwnershipOfEntity( pOther );
  967. //m_hLinkedPortal->PhysicsNotifyOtherOfUntouch( m_hLinkedPortal, pOther );
  968. //pOther->PhysicsNotifyOtherOfUntouch( pOther, m_hLinkedPortal );
  969. }
  970. if( sv_portal_debug_touch.GetBool() )
  971. {
  972. DevMsg( "PORTAL %i TELEPORTING: %s\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname() );
  973. }
  974. #if !defined ( DISABLE_DEBUG_HISTORY )
  975. if ( !IsMarkedForDeletion() )
  976. {
  977. ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "PORTAL %i TELEPORTING: %s\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname() ) );
  978. }
  979. #endif
  980. //do the actual teleportation
  981. {
  982. pOther->SetGroundEntity( NULL );
  983. if( bPlayer )
  984. {
  985. QAngle qTransformedEyeAngles = TransformAnglesToWorldSpace( qPlayerEyeAngles, m_matrixThisToLinked.As3x4() );
  986. qTransformedEyeAngles.x = AngleNormalizePositive( qTransformedEyeAngles.x );
  987. qTransformedEyeAngles.y = AngleNormalizePositive( qTransformedEyeAngles.y );
  988. qTransformedEyeAngles.z = AngleNormalizePositive( qTransformedEyeAngles.z );
  989. pOtherAsPlayer->pl.v_angle = qTransformedEyeAngles;
  990. pOtherAsPlayer->pl.fixangle = FIXANGLE_ABSOLUTE;
  991. pOtherAsPlayer->UpdateVPhysicsPosition( ptNewOrigin, vNewVelocity, 0.0f );
  992. pOtherAsPlayer->Teleport( &ptNewOrigin, &qNewAngles, &vNewVelocity );
  993. //pOtherAsPlayer->UnDuck();
  994. //pOtherAsPlayer->m_angEyeAngles = qTransformedEyeAngles;
  995. //pOtherAsPlayer->pl.v_angle = qTransformedEyeAngles;
  996. //pOtherAsPlayer->pl.fixangle = FIXANGLE_ABSOLUTE;
  997. }
  998. else
  999. {
  1000. if( bNonPhysical )
  1001. {
  1002. pOther->Teleport( &ptNewOrigin, &qNewAngles, &vNewVelocity );
  1003. }
  1004. else
  1005. {
  1006. //doing velocity in two stages as a bug workaround, setting the velocity to anything other than 0 will screw up how objects rest on this entity in the future
  1007. pOther->Teleport( &ptNewOrigin, &qNewAngles, &vec3_origin );
  1008. pOther->ApplyAbsVelocityImpulse( vNewVelocity );
  1009. }
  1010. }
  1011. }
  1012. IPhysicsObject *pPhys = pOther->VPhysicsGetObject();
  1013. if( (pPhys != NULL) && (pPhys->GetGameFlags() & FVPHYSICS_PLAYER_HELD) )
  1014. {
  1015. CPortal_Player *pHoldingPlayer = (CPortal_Player *)GetPlayerHoldingEntity( pOther );
  1016. pHoldingPlayer->ToggleHeldObjectOnOppositeSideOfPortal();
  1017. if ( pHoldingPlayer->IsHeldObjectOnOppositeSideOfPortal() )
  1018. pHoldingPlayer->SetHeldObjectPortal( this );
  1019. else
  1020. pHoldingPlayer->SetHeldObjectPortal( NULL );
  1021. }
  1022. else if( bPlayer )
  1023. {
  1024. CBaseEntity *pHeldEntity = GetPlayerHeldEntity( pOtherAsPlayer );
  1025. if( pHeldEntity )
  1026. {
  1027. pOtherAsPlayer->ToggleHeldObjectOnOppositeSideOfPortal();
  1028. if( pOtherAsPlayer->IsHeldObjectOnOppositeSideOfPortal() )
  1029. {
  1030. pOtherAsPlayer->SetHeldObjectPortal( m_hLinkedPortal );
  1031. }
  1032. else
  1033. {
  1034. pOtherAsPlayer->SetHeldObjectPortal( NULL );
  1035. //we need to make sure the held object and player don't interpenetrate when the player's shape changes
  1036. Vector vTargetPosition;
  1037. QAngle qTargetOrientation;
  1038. UpdateGrabControllerTargetPosition( pOtherAsPlayer, &vTargetPosition, &qTargetOrientation );
  1039. pHeldEntity->Teleport( &vTargetPosition, &qTargetOrientation, 0 );
  1040. FindClosestPassableSpace( pHeldEntity, RemotePortalDataAccess.Placement.vForward );
  1041. }
  1042. }
  1043. //we haven't found a good way of fixing the problem of "how do you reorient an AABB". So we just move the player so that they fit
  1044. //m_hLinkedPortal->ForceEntityToFitInPortalWall( pOtherAsPlayer );
  1045. }
  1046. //force the entity to be touching the other portal right this millisecond
  1047. {
  1048. trace_t Trace;
  1049. memset( &Trace, 0, sizeof(trace_t) );
  1050. //UTIL_TraceEntity( pOther, ptNewOrigin, ptNewOrigin, MASK_SOLID, pOther, COLLISION_GROUP_NONE, &Trace ); //fires off some asserts, and we just need a dummy anyways
  1051. pOther->PhysicsMarkEntitiesAsTouching( m_hLinkedPortal.Get(), Trace );
  1052. m_hLinkedPortal.Get()->PhysicsMarkEntitiesAsTouching( pOther, Trace );
  1053. }
  1054. // Notify the entity that it's being teleported
  1055. // Tell the teleported entity of the portal it has just arrived at
  1056. notify_teleport_params_t paramsTeleport;
  1057. paramsTeleport.prevOrigin = ptOtherOrigin;
  1058. paramsTeleport.prevAngles = qOtherAngles;
  1059. paramsTeleport.physicsRotate = true;
  1060. notify_system_event_params_t eventParams ( &paramsTeleport );
  1061. pOther->NotifySystemEvent( this, NOTIFY_EVENT_TELEPORT, eventParams );
  1062. //notify clients of the teleportation
  1063. {
  1064. CBroadcastRecipientFilter filter;
  1065. filter.MakeReliable();
  1066. UserMessageBegin( filter, "EntityPortalled" );
  1067. WRITE_EHANDLE( this );
  1068. WRITE_EHANDLE( pOther );
  1069. WRITE_FLOAT( ptNewOrigin.x );
  1070. WRITE_FLOAT( ptNewOrigin.y );
  1071. WRITE_FLOAT( ptNewOrigin.z );
  1072. WRITE_FLOAT( qNewAngles.x );
  1073. WRITE_FLOAT( qNewAngles.y );
  1074. WRITE_FLOAT( qNewAngles.z );
  1075. MessageEnd();
  1076. }
  1077. #ifdef _DEBUG
  1078. {
  1079. Vector ptTestCenter = pOther->WorldSpaceCenter();
  1080. float fNewDist, fOldDist;
  1081. fNewDist = RemotePortalDataAccess.Placement.PortalPlane.m_Normal.Dot( ptTestCenter ) - RemotePortalDataAccess.Placement.PortalPlane.m_Dist;
  1082. fOldDist = LocalPortalDataAccess.Placement.PortalPlane.m_Normal.Dot( ptOtherCenter ) - LocalPortalDataAccess.Placement.PortalPlane.m_Dist;
  1083. AssertMsg( fNewDist >= 0.0f, "Entity portalled behind the destination portal." );
  1084. }
  1085. #endif
  1086. pOther->NetworkProp()->NetworkStateForceUpdate();
  1087. if( bPlayer )
  1088. pOtherAsPlayer->pl.NetworkStateChanged();
  1089. //if( bPlayer )
  1090. // NDebugOverlay::EntityBounds( pOther, 0, 255, 0, 128, 60.0f );
  1091. Assert( (bPlayer == false) || (pOtherAsPlayer->m_hPortalEnvironment.Get() == m_hLinkedPortal.Get()) );
  1092. }
  1093. void CProp_Portal::Touch( CBaseEntity *pOther )
  1094. {
  1095. BaseClass::Touch( pOther );
  1096. pOther->Touch( this );
  1097. // Don't do anything on touch if it's not active
  1098. if( !m_bActivated || (m_hLinkedPortal.Get() == NULL) )
  1099. {
  1100. Assert( !m_PortalSimulator.OwnsEntity( pOther ) );
  1101. Assert( !pOther->IsPlayer() || (((CPortal_Player *)pOther)->m_hPortalEnvironment.Get() != this) );
  1102. //I'd really like to fix the root cause, but this will keep the game going
  1103. m_PortalSimulator.ReleaseOwnershipOfEntity( pOther );
  1104. return;
  1105. }
  1106. Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
  1107. (m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
  1108. // Fizzle portal with any moving brush
  1109. Vector vVelocityCheck;
  1110. AngularImpulse vAngularImpulseCheck;
  1111. pOther->GetVelocity( &vVelocityCheck, &vAngularImpulseCheck );
  1112. if( vVelocityCheck != vec3_origin || vAngularImpulseCheck != vec3_origin )
  1113. {
  1114. if ( modelinfo->GetModelType( pOther->GetModel() ) == mod_brush )
  1115. {
  1116. if ( !FClassnameIs( pOther, "func_physbox" ) && !FClassnameIs( pOther, "simple_physics_brush" ) ) // except CPhysBox
  1117. {
  1118. Vector vForward;
  1119. GetVectors( &vForward, NULL, NULL );
  1120. Vector vMin, vMax;
  1121. pOther->GetCollideable()->WorldSpaceSurroundingBounds( &vMin, &vMax );
  1122. if ( UTIL_IsBoxIntersectingPortal( ( vMin + vMax ) / 2.0f, ( vMax - vMin ) / 2.0f - Vector( 2.0f, 2.0f, 2.0f ), this, 0.0f ) )
  1123. {
  1124. DevMsg( "Moving brush intersected portal plane.\n" );
  1125. DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
  1126. Fizzle();
  1127. }
  1128. else
  1129. {
  1130. Vector vOrigin = GetAbsOrigin();
  1131. trace_t tr;
  1132. UTIL_TraceLine( vOrigin, vOrigin - vForward * PORTAL_HALF_DEPTH, MASK_SOLID_BRUSHONLY, NULL, COLLISION_GROUP_NONE, &tr );
  1133. // Something went wrong
  1134. if ( tr.fraction == 1.0f && !tr.startsolid )
  1135. {
  1136. DevMsg( "Surface removed from behind portal.\n" );
  1137. DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
  1138. Fizzle();
  1139. }
  1140. else if ( tr.m_pEnt && tr.m_pEnt->IsMoving() )
  1141. {
  1142. DevMsg( "Surface behind portal is moving.\n" );
  1143. DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
  1144. Fizzle();
  1145. }
  1146. }
  1147. }
  1148. }
  1149. }
  1150. if( m_hLinkedPortal == NULL )
  1151. return;
  1152. //see if we should even be interacting with this object, this is a bugfix where some objects get added to physics environments through walls
  1153. if( !m_PortalSimulator.OwnsEntity( pOther ) )
  1154. {
  1155. //hmm, not in our environment, plane tests, sharing tests
  1156. if( SharedEnvironmentCheck( pOther ) )
  1157. {
  1158. bool bObjectCenterInFrontOfPortal = (m_plane_Origin.normal.Dot( pOther->WorldSpaceCenter() ) > m_plane_Origin.dist);
  1159. bool bIsStuckPlayer = ( pOther->IsPlayer() )? ( !UTIL_IsSpaceEmpty( pOther, pOther->WorldAlignMins(), pOther->WorldAlignMaxs() ) ) : ( false );
  1160. if ( bIsStuckPlayer )
  1161. {
  1162. Assert ( !"Player stuck" );
  1163. DevMsg( "Player in solid behind behind portal %i's plane, Adding to it's environment to run find closest passable space.\n", ((m_bIsPortal2)?(2):(1)) );
  1164. }
  1165. if ( bObjectCenterInFrontOfPortal || bIsStuckPlayer )
  1166. {
  1167. if( sv_portal_debug_touch.GetBool() )
  1168. {
  1169. DevMsg( "Portal %i took control of shared object: %s\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname() );
  1170. }
  1171. #if !defined ( DISABLE_DEBUG_HISTORY )
  1172. if ( !IsMarkedForDeletion() )
  1173. {
  1174. ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i took control of shared object: %s\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname() ) );
  1175. }
  1176. #endif
  1177. //we should be interacting with this object, add it to our environment
  1178. CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
  1179. if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
  1180. pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
  1181. m_PortalSimulator.TakeOwnershipOfEntity( pOther );
  1182. }
  1183. }
  1184. else
  1185. {
  1186. return; //we shouldn't interact with this object
  1187. }
  1188. }
  1189. if( ShouldTeleportTouchingEntity( pOther ) )
  1190. TeleportTouchingEntity( pOther );
  1191. }
  1192. void CProp_Portal::StartTouch( CBaseEntity *pOther )
  1193. {
  1194. BaseClass::StartTouch( pOther );
  1195. // Since prop_portal is a trigger it doesn't send back start touch, so I'm forcing it
  1196. pOther->StartTouch( this );
  1197. if( sv_portal_debug_touch.GetBool() )
  1198. {
  1199. DevMsg( "Portal %i StartTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime );
  1200. }
  1201. #if !defined ( DISABLE_DEBUG_HISTORY )
  1202. if ( !IsMarkedForDeletion() )
  1203. {
  1204. ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i StartTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime ) );
  1205. }
  1206. #endif
  1207. if( (m_hLinkedPortal == NULL) || (m_bActivated == false) )
  1208. return;
  1209. if( CProp_Portal_Shared::IsEntityTeleportable( pOther ) )
  1210. {
  1211. CCollisionProperty *pOtherCollision = pOther->CollisionProp();
  1212. Vector vWorldMins, vWorldMaxs;
  1213. pOtherCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
  1214. Vector ptOtherCenter = (vWorldMins + vWorldMaxs) / 2.0f;
  1215. if( m_plane_Origin.normal.Dot( ptOtherCenter ) > m_plane_Origin.dist )
  1216. {
  1217. //we should be interacting with this object, add it to our environment
  1218. if( SharedEnvironmentCheck( pOther ) )
  1219. {
  1220. Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
  1221. (m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
  1222. CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pOther );
  1223. if( pOwningSimulator && (pOwningSimulator != &m_PortalSimulator) )
  1224. pOwningSimulator->ReleaseOwnershipOfEntity( pOther );
  1225. m_PortalSimulator.TakeOwnershipOfEntity( pOther );
  1226. }
  1227. }
  1228. }
  1229. }
  1230. void CProp_Portal::EndTouch( CBaseEntity *pOther )
  1231. {
  1232. BaseClass::EndTouch( pOther );
  1233. // Since prop_portal is a trigger it doesn't send back end touch, so I'm forcing it
  1234. pOther->EndTouch( this );
  1235. // Don't do anything on end touch if it's not active
  1236. if( !m_bActivated )
  1237. {
  1238. return;
  1239. }
  1240. if( ShouldTeleportTouchingEntity( pOther ) ) //an object passed through the plane and all the way out of the touch box
  1241. TeleportTouchingEntity( pOther );
  1242. else if( pOther->IsPlayer() && //player
  1243. (m_PortalSimulator.m_DataAccess.Placement.vForward.z < -0.7071f) && //most likely falling out of the portal
  1244. (m_PortalSimulator.m_DataAccess.Placement.PortalPlane.m_Normal.Dot( pOther->WorldSpaceCenter() ) < m_PortalSimulator.m_DataAccess.Placement.PortalPlane.m_Dist) && //but behind the portal plane
  1245. (((CPortal_Player *)pOther)->m_Local.m_bInDuckJump) ) //while ducking
  1246. {
  1247. //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)
  1248. DevMsg( "Player pulled feet above the portal they fell out of, postponing Releasing ownership\n" );
  1249. //TeleportTouchingEntity( pOther );
  1250. }
  1251. else
  1252. m_PortalSimulator.ReleaseOwnershipOfEntity( pOther );
  1253. if( sv_portal_debug_touch.GetBool() )
  1254. {
  1255. DevMsg( "Portal %i EndTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime );
  1256. }
  1257. #if !defined( DISABLE_DEBUG_HISTORY )
  1258. if ( !IsMarkedForDeletion() )
  1259. {
  1260. ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Portal %i EndTouch: %s : %f\n", ((m_bIsPortal2)?(2):(1)), pOther->GetClassname(), gpGlobals->curtime ) );
  1261. }
  1262. #endif
  1263. }
  1264. void CProp_Portal::PortalSimulator_TookOwnershipOfEntity( CBaseEntity *pEntity )
  1265. {
  1266. if( pEntity->IsPlayer() )
  1267. ((CPortal_Player *)pEntity)->m_hPortalEnvironment = this;
  1268. }
  1269. void CProp_Portal::PortalSimulator_ReleasedOwnershipOfEntity( CBaseEntity *pEntity )
  1270. {
  1271. if( pEntity->IsPlayer() && (((CPortal_Player *)pEntity)->m_hPortalEnvironment.Get() == this) )
  1272. ((CPortal_Player *)pEntity)->m_hPortalEnvironment = NULL;
  1273. }
  1274. bool CProp_Portal::SharedEnvironmentCheck( CBaseEntity *pEntity )
  1275. {
  1276. Assert( ((m_PortalSimulator.GetLinkedPortalSimulator() == NULL) && (m_hLinkedPortal.Get() == NULL)) ||
  1277. (m_PortalSimulator.GetLinkedPortalSimulator() == &m_hLinkedPortal->m_PortalSimulator) ); //make sure this entity is linked to the same portal as our simulator
  1278. CPortalSimulator *pOwningSimulator = CPortalSimulator::GetSimulatorThatOwnsEntity( pEntity );
  1279. if( (pOwningSimulator == NULL) || (pOwningSimulator == &m_PortalSimulator) )
  1280. {
  1281. //nobody else is claiming ownership
  1282. return true;
  1283. }
  1284. Vector ptCenter = pEntity->WorldSpaceCenter();
  1285. if( (ptCenter - m_PortalSimulator.m_DataAccess.Placement.ptCenter).LengthSqr() < (ptCenter - pOwningSimulator->m_DataAccess.Placement.ptCenter).LengthSqr() )
  1286. return true;
  1287. /*if( !m_hLinkedPortal->m_PortalSimulator.EntityIsInPortalHole( pEntity ) )
  1288. {
  1289. Vector vOtherVelocity;
  1290. pEntity->GetVelocity( &vOtherVelocity );
  1291. if( vOtherVelocity.Dot( m_PortalSimulator.m_DataAccess.Placement.vForward ) < vOtherVelocity.Dot( m_hLinkedPortal->m_PortalSimulator.m_DataAccess.Placement.vForward ) )
  1292. return true; //entity is going towards this portal more than the other
  1293. }*/
  1294. return false;
  1295. //we're in the shared configuration, and the other portal already owns the object, see if we'd be a better caretaker (distance check
  1296. /*CCollisionProperty *pEntityCollision = pEntity->CollisionProp();
  1297. Vector vWorldMins, vWorldMaxs;
  1298. pEntityCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
  1299. Vector ptEntityCenter = (vWorldMins + vWorldMaxs) / 2.0f;
  1300. Vector vEntToThis = GetAbsOrigin() - ptEntityCenter;
  1301. Vector vEntToRemote = m_hLinkedPortal->GetAbsOrigin() - ptEntityCenter;
  1302. return ( vEntToThis.LengthSqr() < vEntToRemote.LengthSqr() );*/
  1303. }
  1304. void CProp_Portal::WakeNearbyEntities( void )
  1305. {
  1306. CBaseEntity* pList[ 1024 ];
  1307. Vector vForward, vUp, vRight;
  1308. GetVectors( &vForward, &vRight, &vUp );
  1309. Vector ptOrigin = GetAbsOrigin();
  1310. QAngle qAngles = GetAbsAngles();
  1311. Vector ptOBBStart = ptOrigin;
  1312. ptOBBStart += vForward * CProp_Portal_Shared::vLocalMins.x;
  1313. ptOBBStart += vRight * CProp_Portal_Shared::vLocalMins.y;
  1314. ptOBBStart += vUp * CProp_Portal_Shared::vLocalMins.z;
  1315. vForward *= CProp_Portal_Shared::vLocalMaxs.x - CProp_Portal_Shared::vLocalMins.x;
  1316. vRight *= CProp_Portal_Shared::vLocalMaxs.y - CProp_Portal_Shared::vLocalMins.y;
  1317. vUp *= CProp_Portal_Shared::vLocalMaxs.z - CProp_Portal_Shared::vLocalMins.z;
  1318. Vector vAABBMins, vAABBMaxs;
  1319. vAABBMins = vAABBMaxs = ptOBBStart;
  1320. for( int i = 1; i != 8; ++i )
  1321. {
  1322. Vector ptTest = ptOBBStart;
  1323. if( i & (1 << 0) ) ptTest += vForward;
  1324. if( i & (1 << 1) ) ptTest += vRight;
  1325. if( i & (1 << 2) ) ptTest += vUp;
  1326. if( ptTest.x < vAABBMins.x ) vAABBMins.x = ptTest.x;
  1327. if( ptTest.y < vAABBMins.y ) vAABBMins.y = ptTest.y;
  1328. if( ptTest.z < vAABBMins.z ) vAABBMins.z = ptTest.z;
  1329. if( ptTest.x > vAABBMaxs.x ) vAABBMaxs.x = ptTest.x;
  1330. if( ptTest.y > vAABBMaxs.y ) vAABBMaxs.y = ptTest.y;
  1331. if( ptTest.z > vAABBMaxs.z ) vAABBMaxs.z = ptTest.z;
  1332. }
  1333. int count = UTIL_EntitiesInBox( pList, 1024, vAABBMins, vAABBMaxs, 0 );
  1334. //Iterate over all the possible targets
  1335. for ( int i = 0; i < count; i++ )
  1336. {
  1337. CBaseEntity *pEntity = pList[i];
  1338. if ( pEntity && (pEntity != this) )
  1339. {
  1340. CCollisionProperty *pEntCollision = pEntity->CollisionProp();
  1341. Vector ptEntityCenter = pEntCollision->GetCollisionOrigin();
  1342. //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
  1343. if( IsOBBIntersectingOBB( ptOrigin, qAngles, CProp_Portal_Shared::vLocalMins, CProp_Portal_Shared::vLocalMaxs,
  1344. ptEntityCenter, pEntCollision->GetCollisionAngles(), pEntCollision->OBBMins(), pEntCollision->OBBMaxs() ) )
  1345. {
  1346. if( FClassnameIs( pEntity, "func_portal_detector" ) )
  1347. {
  1348. // It's a portal detector
  1349. CFuncPortalDetector *pPortalDetector = static_cast<CFuncPortalDetector*>( pEntity );
  1350. if ( pPortalDetector->IsActive() && pPortalDetector->GetLinkageGroupID() == m_iLinkageGroupID )
  1351. {
  1352. // It's detecting this portal's group
  1353. Vector vMin, vMax;
  1354. pPortalDetector->CollisionProp()->WorldSpaceAABB( &vMin, &vMax );
  1355. Vector vBoxCenter = ( vMin + vMax ) * 0.5f;
  1356. Vector vBoxExtents = ( vMax - vMin ) * 0.5f;
  1357. if ( UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, this ) )
  1358. {
  1359. // It's intersecting this portal
  1360. if ( m_bIsPortal2 )
  1361. pPortalDetector->m_OnStartTouchPortal2.FireOutput( this, pPortalDetector );
  1362. else
  1363. pPortalDetector->m_OnStartTouchPortal1.FireOutput( this, pPortalDetector );
  1364. if ( IsActivedAndLinked() )
  1365. {
  1366. pPortalDetector->m_OnStartTouchLinkedPortal.FireOutput( this, pPortalDetector );
  1367. if ( UTIL_IsBoxIntersectingPortal( vBoxCenter, vBoxExtents, m_hLinkedPortal ) )
  1368. {
  1369. pPortalDetector->m_OnStartTouchBothLinkedPortals.FireOutput( this, pPortalDetector );
  1370. }
  1371. }
  1372. }
  1373. }
  1374. }
  1375. pEntity->WakeRestingObjects();
  1376. //pEntity->SetGroundEntity( NULL );
  1377. if ( pEntity->GetMoveType() == MOVETYPE_VPHYSICS )
  1378. {
  1379. IPhysicsObject *pPhysicsObject = pEntity->VPhysicsGetObject();
  1380. if ( pPhysicsObject && pPhysicsObject->IsMoveable() )
  1381. {
  1382. pPhysicsObject->Wake();
  1383. // If the target is debris, convert it to non-debris
  1384. if ( pEntity->GetCollisionGroup() == COLLISION_GROUP_DEBRIS )
  1385. {
  1386. // Interactive debris converts back to debris when it comes to rest
  1387. pEntity->SetCollisionGroup( COLLISION_GROUP_INTERACTIVE_DEBRIS );
  1388. }
  1389. }
  1390. }
  1391. }
  1392. }
  1393. }
  1394. }
  1395. void CProp_Portal::ForceEntityToFitInPortalWall( CBaseEntity *pEntity )
  1396. {
  1397. CCollisionProperty *pCollision = pEntity->CollisionProp();
  1398. Vector vWorldMins, vWorldMaxs;
  1399. pCollision->WorldSpaceAABB( &vWorldMins, &vWorldMaxs );
  1400. Vector ptCenter = pEntity->WorldSpaceCenter(); //(vWorldMins + vWorldMaxs) / 2.0f;
  1401. Vector ptOrigin = pEntity->GetAbsOrigin();
  1402. Vector vEntityCenterToOrigin = ptOrigin - ptCenter;
  1403. Vector ptPortalCenter = GetAbsOrigin();
  1404. Vector vPortalCenterToEntityCenter = ptCenter - ptPortalCenter;
  1405. Vector vPortalForward;
  1406. GetVectors( &vPortalForward, NULL, NULL );
  1407. Vector ptProjectedEntityCenter = ptPortalCenter + ( vPortalForward * vPortalCenterToEntityCenter.Dot( vPortalForward ) );
  1408. Vector ptDest;
  1409. if ( m_PortalSimulator.IsReadyToSimulate() )
  1410. {
  1411. Ray_t ray;
  1412. ray.Init( ptProjectedEntityCenter, ptCenter, vWorldMins - ptCenter, vWorldMaxs - ptCenter );
  1413. trace_t ShortestTrace;
  1414. ShortestTrace.fraction = 2.0f;
  1415. if( m_PortalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable )
  1416. {
  1417. physcollision->TraceBox( ray, m_PortalSimulator.m_DataAccess.Simulation.Static.Wall.Local.Brushes.pCollideable, vec3_origin, vec3_angle, &ShortestTrace );
  1418. }
  1419. /*if( pEnvironment->LocalCollide.pWorldCollide )
  1420. {
  1421. trace_t TempTrace;
  1422. physcollision->TraceBox( ray, pEnvironment->LocalCollide.pWorldCollide, vec3_origin, vec3_angle, &TempTrace );
  1423. if( TempTrace.fraction < ShortestTrace.fraction )
  1424. ShortestTrace = TempTrace;
  1425. }
  1426. if( pEnvironment->LocalCollide.pWallShellCollide )
  1427. {
  1428. trace_t TempTrace;
  1429. physcollision->TraceBox( ray, pEnvironment->LocalCollide.pWallShellCollide, vec3_origin, vec3_angle, &TempTrace );
  1430. if( TempTrace.fraction < ShortestTrace.fraction )
  1431. ShortestTrace = TempTrace;
  1432. }
  1433. if( pEnvironment->LocalCollide.pRemoteWorldWallCollide )
  1434. {
  1435. trace_t TempTrace;
  1436. physcollision->TraceBox( ray, pEnvironment->LocalCollide.pRemoteWorldWallCollide, vec3_origin, vec3_angle, &TempTrace );
  1437. if( TempTrace.fraction < ShortestTrace.fraction )
  1438. ShortestTrace = TempTrace;
  1439. }
  1440. //Add displacement checks here too?
  1441. */
  1442. if( ShortestTrace.fraction < 2.0f )
  1443. {
  1444. Vector ptNewPos = ShortestTrace.endpos + vEntityCenterToOrigin;
  1445. pEntity->Teleport( &ptNewPos, NULL, NULL );
  1446. pEntity->IncrementInterpolationFrame();
  1447. #if !defined ( DISABLE_DEBUG_HISTORY )
  1448. if ( !IsMarkedForDeletion() )
  1449. {
  1450. ADD_DEBUG_HISTORY( HISTORY_PLAYER_DAMAGE, UTIL_VarArgs( "Teleporting %s inside 'ForceEntityToFitInPortalWall'\n", pEntity->GetDebugName() ) );
  1451. }
  1452. #endif
  1453. if( sv_portal_debug_touch.GetBool() )
  1454. {
  1455. DevMsg( "Teleporting %s inside 'ForceEntityToFitInPortalWall'\n", pEntity->GetDebugName() );
  1456. }
  1457. //pEntity->SetAbsOrigin( ShortestTrace.endpos + vEntityCenterToOrigin );
  1458. }
  1459. }
  1460. }
  1461. void CProp_Portal::UpdatePortalTeleportMatrix( void )
  1462. {
  1463. ResetModel();
  1464. //setup our origin plane
  1465. GetVectors( &m_plane_Origin.normal, NULL, NULL );
  1466. m_plane_Origin.dist = m_plane_Origin.normal.Dot( GetAbsOrigin() );
  1467. m_plane_Origin.signbits = SignbitsForPlane( &m_plane_Origin );
  1468. Vector vAbsNormal;
  1469. vAbsNormal.x = fabs(m_plane_Origin.normal.x);
  1470. vAbsNormal.y = fabs(m_plane_Origin.normal.y);
  1471. vAbsNormal.z = fabs(m_plane_Origin.normal.z);
  1472. if( vAbsNormal.x > vAbsNormal.y )
  1473. {
  1474. if( vAbsNormal.x > vAbsNormal.z )
  1475. {
  1476. if( vAbsNormal.x > 0.999f )
  1477. m_plane_Origin.type = PLANE_X;
  1478. else
  1479. m_plane_Origin.type = PLANE_ANYX;
  1480. }
  1481. else
  1482. {
  1483. if( vAbsNormal.z > 0.999f )
  1484. m_plane_Origin.type = PLANE_Z;
  1485. else
  1486. m_plane_Origin.type = PLANE_ANYZ;
  1487. }
  1488. }
  1489. else
  1490. {
  1491. if( vAbsNormal.y > vAbsNormal.z )
  1492. {
  1493. if( vAbsNormal.y > 0.999f )
  1494. m_plane_Origin.type = PLANE_Y;
  1495. else
  1496. m_plane_Origin.type = PLANE_ANYY;
  1497. }
  1498. else
  1499. {
  1500. if( vAbsNormal.z > 0.999f )
  1501. m_plane_Origin.type = PLANE_Z;
  1502. else
  1503. m_plane_Origin.type = PLANE_ANYZ;
  1504. }
  1505. }
  1506. if ( m_hLinkedPortal != NULL )
  1507. {
  1508. CProp_Portal_Shared::UpdatePortalTransformationMatrix( EntityToWorldTransform(), m_hLinkedPortal->EntityToWorldTransform(), &m_matrixThisToLinked );
  1509. m_hLinkedPortal->ResetModel();
  1510. //update the remote portal
  1511. MatrixInverseTR( m_matrixThisToLinked, m_hLinkedPortal->m_matrixThisToLinked );
  1512. }
  1513. else
  1514. {
  1515. m_matrixThisToLinked.Identity(); //don't accidentally teleport objects to zero space
  1516. }
  1517. }
  1518. void CProp_Portal::UpdatePortalLinkage( void )
  1519. {
  1520. if( m_bActivated )
  1521. {
  1522. CProp_Portal *pLink = m_hLinkedPortal.Get();
  1523. if( !(pLink && pLink->m_bActivated) )
  1524. {
  1525. //no old link, or inactive old link
  1526. if( pLink )
  1527. {
  1528. //we had an old link, must be inactive
  1529. if( pLink->m_hLinkedPortal.Get() != NULL )
  1530. pLink->UpdatePortalLinkage();
  1531. pLink = NULL;
  1532. }
  1533. int iPortalCount = s_PortalLinkageGroups[m_iLinkageGroupID].Count();
  1534. if( iPortalCount != 0 )
  1535. {
  1536. CProp_Portal **pPortals = s_PortalLinkageGroups[m_iLinkageGroupID].Base();
  1537. for( int i = 0; i != iPortalCount; ++i )
  1538. {
  1539. CProp_Portal *pCurrentPortal = pPortals[i];
  1540. if( pCurrentPortal == this )
  1541. continue;
  1542. if( pCurrentPortal->m_bActivated && pCurrentPortal->m_hLinkedPortal.Get() == NULL )
  1543. {
  1544. pLink = pCurrentPortal;
  1545. pCurrentPortal->m_hLinkedPortal = this;
  1546. pCurrentPortal->UpdatePortalLinkage();
  1547. break;
  1548. }
  1549. }
  1550. }
  1551. }
  1552. m_hLinkedPortal = pLink;
  1553. if( pLink != NULL )
  1554. {
  1555. CHandle<CProp_Portal> hThis = this;
  1556. CHandle<CProp_Portal> hRemote = pLink;
  1557. this->m_hLinkedPortal = hRemote;
  1558. pLink->m_hLinkedPortal = hThis;
  1559. m_bIsPortal2 = !m_hLinkedPortal->m_bIsPortal2;
  1560. // Initialize mics/speakers
  1561. if( m_hMicrophone == 0 )
  1562. {
  1563. inputdata_t inputdata;
  1564. m_hMicrophone = CreateEntityByName( "env_microphone" );
  1565. CEnvMicrophone *pMicrophone = static_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
  1566. pMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED );
  1567. pMicrophone->AddSpawnFlags( SF_MICROPHONE_SOUND_COMBAT | SF_MICROPHONE_SOUND_WORLD | SF_MICROPHONE_SOUND_PLAYER | SF_MICROPHONE_SOUND_BULLET_IMPACT | SF_MICROPHONE_SOUND_EXPLOSION );
  1568. DispatchSpawn( pMicrophone );
  1569. m_hSpeaker = CreateEntityByName( "env_speaker" );
  1570. CSpeaker *pSpeaker = static_cast<CSpeaker*>( m_hSpeaker.Get() );
  1571. if( !m_bIsPortal2 )
  1572. {
  1573. pSpeaker->SetName( MAKE_STRING( "PortalSpeaker1" ) );
  1574. pMicrophone->SetName( MAKE_STRING( "PortalMic1" ) );
  1575. pMicrophone->Activate();
  1576. pMicrophone->SetSpeakerName( MAKE_STRING( "PortalSpeaker2" ) );
  1577. pMicrophone->SetSensitivity( 10.0f );
  1578. }
  1579. else
  1580. {
  1581. pSpeaker->SetName( MAKE_STRING( "PortalSpeaker2" ) );
  1582. pMicrophone->SetName( MAKE_STRING( "PortalMic2" ) );
  1583. pMicrophone->Activate();
  1584. pMicrophone->SetSpeakerName( MAKE_STRING( "PortalSpeaker1" ) );
  1585. pMicrophone->SetSensitivity( 10.0f );
  1586. }
  1587. }
  1588. if ( m_hLinkedPortal->m_hMicrophone == 0 )
  1589. {
  1590. inputdata_t inputdata;
  1591. m_hLinkedPortal->m_hMicrophone = CreateEntityByName( "env_microphone" );
  1592. CEnvMicrophone *pLinkedMicrophone = static_cast<CEnvMicrophone*>( m_hLinkedPortal->m_hMicrophone.Get() );
  1593. pLinkedMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED );
  1594. pLinkedMicrophone->AddSpawnFlags( SF_MICROPHONE_SOUND_COMBAT | SF_MICROPHONE_SOUND_WORLD | SF_MICROPHONE_SOUND_PLAYER | SF_MICROPHONE_SOUND_BULLET_IMPACT | SF_MICROPHONE_SOUND_EXPLOSION );
  1595. DispatchSpawn( pLinkedMicrophone );
  1596. m_hLinkedPortal->m_hSpeaker = CreateEntityByName( "env_speaker" );
  1597. CSpeaker *pLinkedSpeaker = static_cast<CSpeaker*>( m_hLinkedPortal->m_hSpeaker.Get() );
  1598. if ( !m_bIsPortal2 )
  1599. {
  1600. pLinkedSpeaker->SetName( MAKE_STRING( "PortalSpeaker2" ) );
  1601. pLinkedMicrophone->SetName( MAKE_STRING( "PortalMic2" ) );
  1602. pLinkedMicrophone->Activate();
  1603. pLinkedMicrophone->SetSpeakerName( MAKE_STRING( "PortalSpeaker1" ) );
  1604. pLinkedMicrophone->SetSensitivity( 10.0f );
  1605. }
  1606. else
  1607. {
  1608. pLinkedSpeaker->SetName( MAKE_STRING( "PortalSpeaker1" ) );
  1609. pLinkedMicrophone->SetName( MAKE_STRING( "PortalMic1" ) );
  1610. pLinkedMicrophone->Activate();
  1611. pLinkedMicrophone->SetSpeakerName( MAKE_STRING( "PortalSpeaker2" ) );
  1612. pLinkedMicrophone->SetSensitivity( 10.0f );
  1613. }
  1614. }
  1615. // Set microphone/speaker positions
  1616. Vector vZero( 0.0f, 0.0f, 0.0f );
  1617. CEnvMicrophone *pMicrophone = static_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
  1618. pMicrophone->AddSpawnFlags( SF_MICROPHONE_IGNORE_NONATTENUATED );
  1619. pMicrophone->Teleport( &GetAbsOrigin(), &GetAbsAngles(), &vZero );
  1620. inputdata_t in;
  1621. pMicrophone->InputEnable( in );
  1622. CSpeaker *pSpeaker = static_cast<CSpeaker*>( m_hSpeaker.Get() );
  1623. pSpeaker->Teleport( &GetAbsOrigin(), &GetAbsAngles(), &vZero );
  1624. pSpeaker->InputTurnOn( in );
  1625. UpdatePortalTeleportMatrix();
  1626. }
  1627. else
  1628. {
  1629. m_PortalSimulator.DetachFromLinked();
  1630. m_PortalSimulator.ReleaseAllEntityOwnership();
  1631. }
  1632. Vector ptCenter = GetAbsOrigin();
  1633. QAngle qAngles = GetAbsAngles();
  1634. m_PortalSimulator.MoveTo( ptCenter, qAngles );
  1635. if( pLink )
  1636. m_PortalSimulator.AttachTo( &pLink->m_PortalSimulator );
  1637. if( m_pAttachedCloningArea )
  1638. m_pAttachedCloningArea->UpdatePosition();
  1639. }
  1640. else
  1641. {
  1642. CProp_Portal *pRemote = m_hLinkedPortal;
  1643. //apparently we've been deactivated
  1644. m_PortalSimulator.DetachFromLinked();
  1645. m_PortalSimulator.ReleaseAllEntityOwnership();
  1646. m_hLinkedPortal = NULL;
  1647. if( pRemote )
  1648. pRemote->UpdatePortalLinkage();
  1649. }
  1650. }
  1651. void CProp_Portal::PlacePortal( const Vector &vOrigin, const QAngle &qAngles, float fPlacementSuccess, bool bDelay /*= false*/ )
  1652. {
  1653. Vector vOldOrigin = GetLocalOrigin();
  1654. QAngle qOldAngles = GetLocalAngles();
  1655. Vector vNewOrigin = vOrigin;
  1656. QAngle qNewAngles = qAngles;
  1657. UTIL_TestForOrientationVolumes( qNewAngles, vNewOrigin, this );
  1658. if ( sv_portal_placement_never_fail.GetBool() )
  1659. {
  1660. fPlacementSuccess = PORTAL_FIZZLE_SUCCESS;
  1661. }
  1662. if ( fPlacementSuccess < 0.5f )
  1663. {
  1664. // Prepare fizzle
  1665. m_vDelayedPosition = vOrigin;
  1666. m_qDelayedAngles = qAngles;
  1667. if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_CANT_FIT )
  1668. m_iDelayedFailure = PORTAL_FIZZLE_CANT_FIT;
  1669. else if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_OVERLAP_LINKED )
  1670. m_iDelayedFailure = PORTAL_FIZZLE_OVERLAPPED_LINKED;
  1671. else if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_INVALID_VOLUME )
  1672. m_iDelayedFailure = PORTAL_FIZZLE_BAD_VOLUME;
  1673. else if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_INVALID_SURFACE )
  1674. m_iDelayedFailure = PORTAL_FIZZLE_BAD_SURFACE;
  1675. else if ( fPlacementSuccess == PORTAL_ANALOG_SUCCESS_PASSTHROUGH_SURFACE )
  1676. m_iDelayedFailure = PORTAL_FIZZLE_NONE;
  1677. CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
  1678. if( pPortalGun )
  1679. {
  1680. CPortal_Player *pFiringPlayer = dynamic_cast<CPortal_Player *>( pPortalGun->GetOwner() );
  1681. if( pFiringPlayer )
  1682. {
  1683. g_PortalGameStats.Event_PortalPlacement( pFiringPlayer->GetAbsOrigin(), vOrigin, m_iDelayedFailure );
  1684. }
  1685. }
  1686. return;
  1687. }
  1688. if ( !bDelay )
  1689. {
  1690. m_vDelayedPosition = vNewOrigin;
  1691. m_qDelayedAngles = qNewAngles;
  1692. m_iDelayedFailure = PORTAL_FIZZLE_SUCCESS;
  1693. NewLocation( vNewOrigin, qNewAngles );
  1694. }
  1695. else
  1696. {
  1697. m_vDelayedPosition = vNewOrigin;
  1698. m_qDelayedAngles = qNewAngles;
  1699. m_iDelayedFailure = PORTAL_FIZZLE_SUCCESS;
  1700. }
  1701. CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
  1702. if( pPortalGun )
  1703. {
  1704. CPortal_Player *pFiringPlayer = dynamic_cast<CPortal_Player *>( pPortalGun->GetOwner() );
  1705. if( pFiringPlayer )
  1706. {
  1707. g_PortalGameStats.Event_PortalPlacement( pFiringPlayer->GetAbsOrigin(), vOrigin, m_iDelayedFailure );
  1708. }
  1709. }
  1710. }
  1711. void CProp_Portal::NewLocation( const Vector &vOrigin, const QAngle &qAngles )
  1712. {
  1713. // Tell our physics environment to stop simulating it's entities.
  1714. // Fast moving objects can pass through the hole this frame while it's in the old location.
  1715. m_PortalSimulator.ReleaseAllEntityOwnership();
  1716. Vector vOldForward;
  1717. GetVectors( &vOldForward, 0, 0 );
  1718. m_vPrevForward = vOldForward;
  1719. WakeNearbyEntities();
  1720. Teleport( &vOrigin, &qAngles, 0 );
  1721. if ( m_hMicrophone )
  1722. {
  1723. CEnvMicrophone *pMicrophone = static_cast<CEnvMicrophone*>( m_hMicrophone.Get() );
  1724. pMicrophone->Teleport( &vOrigin, &qAngles, 0 );
  1725. inputdata_t in;
  1726. pMicrophone->InputEnable( in );
  1727. }
  1728. if ( m_hSpeaker )
  1729. {
  1730. CSpeaker *pSpeaker = static_cast<CSpeaker*>( m_hSpeaker.Get() );
  1731. pSpeaker->Teleport( &vOrigin, &qAngles, 0 );
  1732. inputdata_t in;
  1733. pSpeaker->InputTurnOn( in );
  1734. }
  1735. CreateSounds();
  1736. if ( m_pAmbientSound )
  1737. {
  1738. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1739. controller.SoundChangeVolume( m_pAmbientSound, 0.4, 0.1 );
  1740. }
  1741. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_particles" ) : ( "portal_1_particles" ) ), PATTACH_POINT_FOLLOW, this, "particles_2", true );
  1742. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_edge" ) : ( "portal_1_edge" ) ), PATTACH_POINT_FOLLOW, this, "particlespin" );
  1743. //if the other portal should be static, let's not punch stuff resting on it
  1744. bool bOtherShouldBeStatic = false;
  1745. if( !m_hLinkedPortal )
  1746. bOtherShouldBeStatic = true;
  1747. m_bActivated = true;
  1748. UpdatePortalLinkage();
  1749. UpdatePortalTeleportMatrix();
  1750. // Update the four corners of this portal for faster reference
  1751. UpdateCorners();
  1752. WakeNearbyEntities();
  1753. if ( m_hLinkedPortal )
  1754. {
  1755. m_hLinkedPortal->WakeNearbyEntities();
  1756. if( !bOtherShouldBeStatic )
  1757. {
  1758. m_hLinkedPortal->PunchAllPenetratingPlayers();
  1759. }
  1760. }
  1761. if ( m_bIsPortal2 )
  1762. {
  1763. EmitSound( "Portal.open_red" );
  1764. }
  1765. else
  1766. {
  1767. EmitSound( "Portal.open_blue" );
  1768. }
  1769. }
  1770. void CProp_Portal::InputSetActivatedState( inputdata_t &inputdata )
  1771. {
  1772. m_bActivated = inputdata.value.Bool();
  1773. m_hPlacedBy = NULL;
  1774. if ( m_bActivated )
  1775. {
  1776. Vector vOrigin;
  1777. vOrigin = GetAbsOrigin();
  1778. Vector vForward, vUp;
  1779. GetVectors( &vForward, 0, &vUp );
  1780. CTraceFilterSimpleClassnameList baseFilter( this, COLLISION_GROUP_NONE );
  1781. UTIL_Portal_Trace_Filter( &baseFilter );
  1782. CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
  1783. trace_t tr;
  1784. UTIL_TraceLine( vOrigin + vForward, vOrigin + vForward * -8.0f, MASK_SHOT_PORTAL, &traceFilterPortalShot, &tr );
  1785. QAngle qAngles;
  1786. VectorAngles( tr.plane.normal, vUp, qAngles );
  1787. float fPlacementSuccess = VerifyPortalPlacement( this, tr.endpos, qAngles, PORTAL_PLACED_BY_FIXED );
  1788. PlacePortal( tr.endpos, qAngles, fPlacementSuccess );
  1789. // If the fixed portal is overlapping a portal that was placed before it... kill it!
  1790. if ( fPlacementSuccess )
  1791. {
  1792. IsPortalOverlappingOtherPortals( this, vOrigin, GetAbsAngles(), true );
  1793. CreateSounds();
  1794. if ( m_pAmbientSound )
  1795. {
  1796. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1797. controller.SoundChangeVolume( m_pAmbientSound, 0.4, 0.1 );
  1798. }
  1799. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_particles" ) : ( "portal_1_particles" ) ), PATTACH_POINT_FOLLOW, this, "particles_2", true );
  1800. DispatchParticleEffect( ( ( m_bIsPortal2 ) ? ( "portal_2_edge" ) : ( "portal_1_edge" ) ), PATTACH_POINT_FOLLOW, this, "particlespin" );
  1801. if ( m_bIsPortal2 )
  1802. {
  1803. EmitSound( "Portal.open_red" );
  1804. }
  1805. else
  1806. {
  1807. EmitSound( "Portal.open_blue" );
  1808. }
  1809. }
  1810. }
  1811. else
  1812. {
  1813. if ( m_pAmbientSound )
  1814. {
  1815. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  1816. controller.SoundChangeVolume( m_pAmbientSound, 0.0, 0.0 );
  1817. }
  1818. StopParticleEffects( this );
  1819. }
  1820. UpdatePortalTeleportMatrix();
  1821. UpdatePortalLinkage();
  1822. }
  1823. void CProp_Portal::InputFizzle( inputdata_t &inputdata )
  1824. {
  1825. DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
  1826. Fizzle();
  1827. }
  1828. //-----------------------------------------------------------------------------
  1829. // Purpose: Map can call new location, so far it's only for debugging purposes so it's not made to be very robust.
  1830. // Input : &inputdata - String with 6 float entries with space delimiters, location and orientation
  1831. //-----------------------------------------------------------------------------
  1832. void CProp_Portal::InputNewLocation( inputdata_t &inputdata )
  1833. {
  1834. char sLocationStats[MAX_PATH];
  1835. Q_strncpy( sLocationStats, inputdata.value.String(), sizeof(sLocationStats) );
  1836. // first 3 are location of new origin
  1837. Vector vNewOrigin;
  1838. char* pTok = strtok( sLocationStats, " " );
  1839. vNewOrigin.x = atof(pTok);
  1840. pTok = strtok( NULL, " " );
  1841. vNewOrigin.y = atof(pTok);
  1842. pTok = strtok( NULL, " " );
  1843. vNewOrigin.z = atof(pTok);
  1844. // Next 3 entries are new angles
  1845. QAngle vNewAngles;
  1846. pTok = strtok( NULL, " " );
  1847. vNewAngles.x = atof(pTok);
  1848. pTok = strtok( NULL, " " );
  1849. vNewAngles.y = atof(pTok);
  1850. pTok = strtok( NULL, " " );
  1851. vNewAngles.z = atof(pTok);
  1852. // Call main placement function (skipping placement rules)
  1853. NewLocation( vNewOrigin, vNewAngles );
  1854. }
  1855. void CProp_Portal::UpdateCorners()
  1856. {
  1857. Vector vOrigin = GetAbsOrigin();
  1858. Vector vUp, vRight;
  1859. GetVectors( NULL, &vRight, &vUp );
  1860. for ( int i = 0; i < 4; ++i )
  1861. {
  1862. Vector vAddPoint = vOrigin;
  1863. vAddPoint += vRight * ((i & (1<<0))?(PORTAL_HALF_WIDTH):(-PORTAL_HALF_WIDTH));
  1864. vAddPoint += vUp * ((i & (1<<1))?(PORTAL_HALF_HEIGHT):(-PORTAL_HALF_HEIGHT));
  1865. m_vPortalCorners[i] = vAddPoint;
  1866. }
  1867. }
  1868. void CProp_Portal::ChangeLinkageGroup( unsigned char iLinkageGroupID )
  1869. {
  1870. Assert( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) != -1 );
  1871. s_PortalLinkageGroups[m_iLinkageGroupID].FindAndRemove( this );
  1872. s_PortalLinkageGroups[iLinkageGroupID].AddToTail( this );
  1873. m_iLinkageGroupID = iLinkageGroupID;
  1874. }
  1875. CProp_Portal *CProp_Portal::FindPortal( unsigned char iLinkageGroupID, bool bPortal2, bool bCreateIfNothingFound /*= false*/ )
  1876. {
  1877. int iPortalCount = s_PortalLinkageGroups[iLinkageGroupID].Count();
  1878. if( iPortalCount != 0 )
  1879. {
  1880. CProp_Portal *pFoundInactive = NULL;
  1881. CProp_Portal **pPortals = s_PortalLinkageGroups[iLinkageGroupID].Base();
  1882. for( int i = 0; i != iPortalCount; ++i )
  1883. {
  1884. if( pPortals[i]->m_bIsPortal2 == bPortal2 )
  1885. {
  1886. if( pPortals[i]->m_bActivated )
  1887. return pPortals[i];
  1888. else
  1889. pFoundInactive = pPortals[i];
  1890. }
  1891. }
  1892. if( pFoundInactive )
  1893. return pFoundInactive;
  1894. }
  1895. if( bCreateIfNothingFound )
  1896. {
  1897. CProp_Portal *pPortal = (CProp_Portal *)CreateEntityByName( "prop_portal" );
  1898. pPortal->m_iLinkageGroupID = iLinkageGroupID;
  1899. pPortal->m_bIsPortal2 = bPortal2;
  1900. DispatchSpawn( pPortal );
  1901. return pPortal;
  1902. }
  1903. return NULL;
  1904. }
  1905. const CUtlVector<CProp_Portal *> *CProp_Portal::GetPortalLinkageGroup( unsigned char iLinkageGroupID )
  1906. {
  1907. return &s_PortalLinkageGroups[iLinkageGroupID];
  1908. }