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.

988 lines
30 KiB

  1. //===== Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #include "cbase.h"
  8. #include "prop_portal_shared.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 "weapon_portalgun.h"
  25. #include "portal_placement.h"
  26. #include "physicsshadowclone.h"
  27. #include "particle_parse.h"
  28. #include "rumble_shared.h"
  29. #include "func_portal_orientation.h"
  30. #include "env_debughistory.h"
  31. #include "tier1/callqueue.h"
  32. #include "baseprojector.h"
  33. #include "tier1/convar.h"
  34. #include "iextpropportallocator.h"
  35. #include "matchmaking/imatchframework.h"
  36. // memdbgon must be the last include file in a .cpp file!!!
  37. #include "tier0/memdbgon.h"
  38. #ifdef PORTAL2
  39. extern bool UTIL_FizzlePlayerPhotos( CPortal_Player *pPlayer );
  40. #endif // PORTAL2
  41. static CUtlVector<CProp_Portal *> s_PortalLinkageGroups[256];
  42. const char *CProp_Portal::s_szDelayedPlacementThinkContext = "CProp_Portal::DelayedPlacementThink";
  43. extern ConVar sv_portal_placement_never_fail;
  44. extern ConVar use_server_portal_particles;
  45. BEGIN_DATADESC( CProp_Portal )
  46. //saving
  47. DEFINE_KEYFIELD( m_iLinkageGroupID, FIELD_CHARACTER, "LinkageGroupID" ),
  48. DEFINE_KEYFIELD( m_bActivated, FIELD_BOOLEAN, "Activated" ),
  49. DEFINE_KEYFIELD( m_bOldActivatedState, FIELD_BOOLEAN, "OldActivated" ),
  50. DEFINE_KEYFIELD( m_bIsPortal2, FIELD_BOOLEAN, "PortalTwo" ),
  51. DEFINE_FIELD( m_NotifyOnPortalled, FIELD_EHANDLE ),
  52. DEFINE_FIELD( m_hFiredByPlayer, FIELD_EHANDLE ),
  53. DEFINE_SOUNDPATCH( m_pAmbientSound ),
  54. // Function Pointers
  55. DEFINE_THINKFUNC( DelayedPlacementThink ),
  56. DEFINE_INPUTFUNC( FIELD_BOOLEAN, "SetActivatedState", InputSetActivatedState ),
  57. DEFINE_INPUTFUNC( FIELD_VOID, "Fizzle", InputFizzle ),
  58. DEFINE_INPUTFUNC( FIELD_STRING, "NewLocation", InputNewLocation ),
  59. DEFINE_INPUTFUNC( FIELD_STRING, "Resize", InputResize ),
  60. DEFINE_INPUTFUNC( FIELD_INTEGER, "SetLinkageGroupId", InputSetLinkageGroupId ),
  61. END_DATADESC()
  62. IMPLEMENT_SERVERCLASS_ST( CProp_Portal, DT_Prop_Portal )
  63. SendPropEHandle( SENDINFO( m_hFiredByPlayer ) ),
  64. SendPropInt( SENDINFO( m_nPlacementAttemptParity ), EF_PARITY_BITS, SPROP_UNSIGNED ),
  65. END_SEND_TABLE()
  66. LINK_ENTITY_TO_CLASS( prop_portal, CProp_Portal );
  67. CProp_Portal::CProp_Portal( void )
  68. {
  69. if( !ms_DefaultPortalSizeInitialized )
  70. {
  71. ms_DefaultPortalSizeInitialized = true; // for CEG protection
  72. CEG_GCV_PRE();
  73. ms_DefaultPortalHalfHeight = CEG_GET_CONSTANT_VALUE( DefaultPortalHalfHeight ); // only protecting one to reduce the cost of first-portal check
  74. CEG_GCV_POST();
  75. }
  76. m_FizzleEffect = PORTAL_FIZZLE_KILLED;
  77. CProp_Portal_Shared::AllPortals.AddToTail( this );
  78. }
  79. CProp_Portal::~CProp_Portal( void )
  80. {
  81. CProp_Portal_Shared::AllPortals.FindAndRemove( this );
  82. s_PortalLinkageGroups[m_iLinkageGroupID].FindAndRemove( this );
  83. }
  84. void CProp_Portal::Precache( void )
  85. {
  86. PrecacheScriptSound( "Portal.ambient_loop" );
  87. PrecacheScriptSound( "Portal.open_blue" );
  88. PrecacheScriptSound( "Portal.open_red" );
  89. PrecacheScriptSound( "Portal.close_blue" );
  90. PrecacheScriptSound( "Portal.close_red" );
  91. PrecacheScriptSound( "Portal.fizzle_moved" );
  92. PrecacheScriptSound( "Portal.fizzle_invalid_surface" );
  93. PrecacheModel( "models/portals/portal1.mdl" );
  94. PrecacheModel( "models/portals/portal2.mdl" );
  95. //PrecacheParticleSystem( "portal_1_particles" );
  96. //PrecacheParticleSystem( "portal_2_particles" );
  97. //PrecacheParticleSystem( "portal_1_edge" );
  98. //PrecacheParticleSystem( "portal_2_edge" );
  99. //PrecacheParticleSystem( "portal_1_close" );
  100. //PrecacheParticleSystem( "portal_2_close" );
  101. //PrecacheParticleSystem( "portal_1_badsurface" );
  102. //PrecacheParticleSystem( "portal_2_badsurface" );
  103. //PrecacheParticleSystem( "portal_1_success" );
  104. //PrecacheParticleSystem( "portal_2_success" );
  105. // adjustable color for coop, two colorable systems instead of four unique -mtw
  106. // need two systems here because they spin different directions
  107. PrecacheParticleSystem( "portal_edge" );
  108. PrecacheParticleSystem( "portal_edge_reverse" );
  109. PrecacheParticleSystem( "portal_close" );
  110. PrecacheParticleSystem( "portal_badsurface" );
  111. PrecacheParticleSystem( "portal_success" );
  112. BaseClass::Precache();
  113. }
  114. void CProp_Portal::CreateSounds()
  115. {
  116. if (!m_pAmbientSound)
  117. {
  118. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  119. CPASAttenuationFilter filter( this );
  120. m_pAmbientSound = controller.SoundCreate( filter, entindex(), "Portal.ambient_loop" );
  121. controller.Play( m_pAmbientSound, 0, 100 );
  122. }
  123. }
  124. void CProp_Portal::StopLoopingSounds()
  125. {
  126. if ( m_pAmbientSound )
  127. {
  128. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  129. controller.SoundDestroy( m_pAmbientSound );
  130. m_pAmbientSound = NULL;
  131. }
  132. BaseClass::StopLoopingSounds();
  133. }
  134. class CPortalServerDllPropPortalLocator : public IPortalServerDllPropPortalLocator
  135. {
  136. public:
  137. virtual void LocateAllPortals( CUtlVector<PortalInfo_t> &arrPortals )
  138. {
  139. for ( int iLinkageGroupID = 0; iLinkageGroupID < 3; ++iLinkageGroupID )
  140. {
  141. for ( int nPortal = 0; nPortal < 2; ++nPortal )
  142. {
  143. CProp_Portal *pPortal = CProp_Portal::FindPortal( iLinkageGroupID, (nPortal != 0), false );
  144. if ( !pPortal )
  145. continue;
  146. const Vector &vecOrigin = pPortal->GetAbsOrigin();
  147. const QAngle &vecAngle = pPortal->GetAbsAngles();
  148. PortalInfo_t pi;
  149. pi.iLinkageGroupId = iLinkageGroupID;
  150. pi.nPortal = nPortal;
  151. pi.vecOrigin = vecOrigin;
  152. pi.vecAngle = vecAngle;
  153. arrPortals.AddToTail( pi );
  154. }
  155. }
  156. }
  157. } s_PortalServerDllPropPortalLocator;
  158. void CProp_Portal::Spawn( void )
  159. {
  160. Precache();
  161. AddToLinkageGroup();
  162. ResetModel();
  163. if( (GetHalfWidth() <= 0) || (GetHalfHeight() <= 0) )
  164. Resize( ms_DefaultPortalHalfWidth, ms_DefaultPortalHalfHeight );
  165. BaseClass::Spawn();
  166. static bool s_bPortalLocatorForClientRegistered;
  167. if ( !s_bPortalLocatorForClientRegistered && g_pMatchFramework )
  168. {
  169. s_bPortalLocatorForClientRegistered = true;
  170. g_pMatchFramework->GetMatchExtensions()->RegisterExtensionInterface( IEXTPROPPORTALLOCATOR_INTERFACE_NAME, &s_PortalServerDllPropPortalLocator );
  171. }
  172. }
  173. void CProp_Portal::OnRestore()
  174. {
  175. BaseClass::OnRestore();
  176. if ( IsActive() )
  177. {
  178. // Place the particles in position
  179. DispatchPortalPlacementParticles( m_bIsPortal2 );
  180. }
  181. AddToLinkageGroup();
  182. }
  183. ConVar sv_portals_block_other_players( "sv_portals_block_other_players", "0", FCVAR_REPLICATED | FCVAR_DEVELOPMENTONLY );
  184. void CProp_Portal::StartTouch( CBaseEntity *pOther )
  185. {
  186. if( sv_portals_block_other_players.GetBool() && g_pGameRules->IsMultiplayer() )
  187. {
  188. if( pOther->IsPlayer() && (m_hFiredByPlayer.Get() != pOther) )
  189. return; //block the interaction
  190. }
  191. return BaseClass::StartTouch( pOther );
  192. }
  193. void CProp_Portal::Touch( CBaseEntity *pOther )
  194. {
  195. if( sv_portals_block_other_players.GetBool() && g_pGameRules->IsMultiplayer() )
  196. {
  197. if( pOther->IsPlayer() && (m_hFiredByPlayer.Get() != pOther) )
  198. return; //block the interaction
  199. }
  200. return BaseClass::Touch( pOther );
  201. }
  202. void CProp_Portal::EndTouch( CBaseEntity *pOther )
  203. {
  204. if( sv_portals_block_other_players.GetBool() && g_pGameRules->IsMultiplayer() )
  205. {
  206. if( pOther->IsPlayer() && (m_hFiredByPlayer.Get() != pOther) )
  207. return; //block the interaction
  208. }
  209. return BaseClass::EndTouch( pOther );
  210. }
  211. void DumpActiveCollision( const CPortalSimulator *pPortalSimulator, const char *szFileName );
  212. void PortalSimulatorDumps_DumpCollideToGlView( CPhysCollide *pCollide, const Vector &origin, const QAngle &angles, float fColorScale, const char *pFilename );
  213. void CProp_Portal::ResetModel( void )
  214. {
  215. if( !m_bIsPortal2 )
  216. SetModel( "models/portals/portal1.mdl" );
  217. else
  218. SetModel( "models/portals/portal2.mdl" );
  219. if( IsMobile() || ((m_hLinkedPortal.Get() != NULL) && !m_hLinkedPortal->IsMobile()) )
  220. {
  221. SetSize( GetLocalMins(), Vector( 4.0f, m_fNetworkHalfWidth, m_fNetworkHalfHeight ) );
  222. }
  223. else
  224. {
  225. SetSize( GetLocalMins(), GetLocalMaxs() );
  226. }
  227. SetSolid( SOLID_OBB );
  228. SetSolidFlags( FSOLID_TRIGGER | FSOLID_NOT_SOLID | FSOLID_CUSTOMBOXTEST | FSOLID_CUSTOMRAYTEST );
  229. }
  230. void CProp_Portal::DoFizzleEffect( int iEffect, bool bDelayedPos /*= true*/ )
  231. {
  232. m_vAudioOrigin = ( ( bDelayedPos ) ? ( m_vDelayedPosition ) : ( m_vOldPosition ) );
  233. CEffectData fxData;
  234. fxData.m_vAngles = ( ( bDelayedPos ) ? ( m_qDelayedAngles ) : ( m_qOldAngles ) );
  235. Vector vForward, vUp;
  236. AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
  237. fxData.m_vOrigin = m_vAudioOrigin + vForward * 1.0f;
  238. fxData.m_nColor = ( ( m_bIsPortal2 ) ? ( 1 ) : ( 0 ) );
  239. EmitSound_t ep;
  240. CPASAttenuationFilter filter( m_vDelayedPosition );
  241. ep.m_nChannel = CHAN_STATIC;
  242. ep.m_flVolume = 1.0f;
  243. ep.m_pOrigin = &m_vAudioOrigin;
  244. int nTeam = GetTeamNumber();
  245. int nPortalNum = m_bIsPortal2 ? 2 : 1;
  246. // Rumble effects on the firing player (if one exists)
  247. CWeaponPortalgun *pPortalGun = dynamic_cast<CWeaponPortalgun*>( m_hPlacedBy.Get() );
  248. CBasePlayer* pPlayer = NULL;
  249. if ( pPortalGun )
  250. {
  251. pPlayer = (CBasePlayer*)pPortalGun->GetOwner();
  252. if ( pPlayer )
  253. {
  254. if ( iEffect != PORTAL_FIZZLE_CLOSE &&
  255. iEffect != PORTAL_FIZZLE_SUCCESS &&
  256. iEffect != PORTAL_FIZZLE_NONE )
  257. {
  258. pPlayer->RumbleEffect( RUMBLE_PORTAL_PLACEMENT_FAILURE, 0, RUMBLE_FLAGS_NONE );
  259. }
  260. nTeam = pPlayer->GetTeamNumber();
  261. }
  262. }
  263. // Pick a fizzle effect
  264. switch ( iEffect )
  265. {
  266. case PORTAL_FIZZLE_CANT_FIT:
  267. //DispatchEffect( "PortalFizzleCantFit", fxData );
  268. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  269. VectorAngles( vUp, vForward, fxData.m_vAngles );
  270. CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
  271. break;
  272. case PORTAL_FIZZLE_OVERLAPPED_LINKED:
  273. {
  274. /*CProp_Portal *pLinkedPortal = m_hLinkedPortal;
  275. if ( pLinkedPortal )
  276. {
  277. Vector vLinkedForward;
  278. pLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
  279. fxData.m_vStart = pLink3edPortal->GetAbsOrigin() + vLinkedForward * 5.0f;
  280. }*/
  281. //DispatchEffect( "PortalFizzleOverlappedLinked", fxData );
  282. VectorAngles( vUp, vForward, fxData.m_vAngles );
  283. CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
  284. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  285. break;
  286. }
  287. case PORTAL_FIZZLE_BAD_VOLUME:
  288. //DispatchEffect( "PortalFizzleBadVolume", fxData );
  289. VectorAngles( vUp, vForward, fxData.m_vAngles );
  290. CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
  291. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  292. break;
  293. case PORTAL_FIZZLE_BAD_SURFACE:
  294. //DispatchEffect( "PortalFizzleBadSurface", fxData );
  295. VectorAngles( vUp, vForward, fxData.m_vAngles );
  296. CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
  297. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  298. break;
  299. case PORTAL_FIZZLE_KILLED:
  300. //DispatchEffect( "PortalFizzleKilled", fxData );
  301. VectorAngles( vUp, vForward, fxData.m_vAngles );
  302. CreatePortalEffect( pPlayer, PORTAL_FIZZLE_CLOSE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
  303. ep.m_pSoundName = "Portal.fizzle_moved";
  304. break;
  305. case PORTAL_FIZZLE_CLEANSER:
  306. //DispatchEffect( "PortalFizzleCleanser", fxData );
  307. VectorAngles( vUp, vForward, fxData.m_vAngles );
  308. CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
  309. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  310. break;
  311. case PORTAL_FIZZLE_CLOSE:
  312. //DispatchEffect( "PortalFizzleKilled", fxData );
  313. VectorAngles( vUp, vForward, fxData.m_vAngles );
  314. CreatePortalEffect( pPlayer, PORTAL_FIZZLE_CLOSE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
  315. ep.m_pSoundName = ( ( m_bIsPortal2 ) ? ( "Portal.close_red" ) : ( "Portal.close_blue" ) );
  316. break;
  317. case PORTAL_FIZZLE_NEAR_BLUE:
  318. {
  319. if ( !m_bIsPortal2 )
  320. {
  321. Vector vLinkedForward;
  322. m_hLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
  323. fxData.m_vOrigin = m_hLinkedPortal->GetAbsOrigin() + vLinkedForward * 16.0f;
  324. fxData.m_vAngles = m_hLinkedPortal->GetAbsAngles();
  325. }
  326. else
  327. {
  328. GetVectors( &vForward, NULL, NULL );
  329. fxData.m_vOrigin = GetAbsOrigin() + vForward * 16.0f;
  330. fxData.m_vAngles = GetAbsAngles();
  331. }
  332. //DispatchEffect( "PortalFizzleNear", fxData );
  333. AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
  334. VectorAngles( vUp, vForward, fxData.m_vAngles );
  335. CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
  336. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  337. break;
  338. }
  339. case PORTAL_FIZZLE_NEAR_RED:
  340. {
  341. if ( m_bIsPortal2 )
  342. {
  343. Vector vLinkedForward;
  344. m_hLinkedPortal->GetVectors( &vLinkedForward, NULL, NULL );
  345. fxData.m_vOrigin = m_hLinkedPortal->GetAbsOrigin() + vLinkedForward * 16.0f;
  346. fxData.m_vAngles = m_hLinkedPortal->GetAbsAngles();
  347. }
  348. else
  349. {
  350. GetVectors( &vForward, NULL, NULL );
  351. fxData.m_vOrigin = GetAbsOrigin() + vForward * 16.0f;
  352. fxData.m_vAngles = GetAbsAngles();
  353. }
  354. //DispatchEffect( "PortalFizzleNear", fxData );
  355. AngleVectors( fxData.m_vAngles, &vForward, &vUp, NULL );
  356. VectorAngles( vUp, vForward, fxData.m_vAngles );
  357. CreatePortalEffect( pPlayer, PORTAL_FIZZLE_BAD_SURFACE, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
  358. ep.m_pSoundName = "Portal.fizzle_invalid_surface";
  359. break;
  360. }
  361. case PORTAL_FIZZLE_SUCCESS:
  362. VectorAngles( vUp, vForward, fxData.m_vAngles );
  363. CreatePortalEffect( pPlayer, PORTAL_FIZZLE_SUCCESS, fxData.m_vOrigin, fxData.m_vAngles, nTeam, nPortalNum );
  364. // Don't make a sound!
  365. return;
  366. case PORTAL_FIZZLE_NONE:
  367. // Don't do anything!
  368. return;
  369. }
  370. EmitSound( filter, SOUND_FROM_WORLD, ep );
  371. }
  372. //-----------------------------------------------------------------------------
  373. // Purpose: Create the portal effect
  374. //-----------------------------------------------------------------------------
  375. void CProp_Portal::CreatePortalEffect( CBasePlayer* pPlayer, int iEffect, Vector vecOrigin, QAngle qAngles, int nTeam, int nPortalNum )
  376. {
  377. if ( !pPlayer || iEffect == PORTAL_FIZZLE_NONE )
  378. return;
  379. CBroadcastRecipientFilter filter;
  380. filter.MakeReliable();
  381. // remove the player who shot it because we handle this in
  382. // the client code and don't need to send a message
  383. if ( pPlayer->m_bPredictionEnabled )
  384. {
  385. filter.RemoveRecipient( pPlayer );
  386. }
  387. UserMessageBegin( filter, "PortalFX_Surface" );
  388. WRITE_SHORT( entindex() );
  389. WRITE_SHORT( pPlayer->entindex() );
  390. WRITE_BYTE( nTeam );
  391. WRITE_BYTE( nPortalNum );
  392. WRITE_BYTE( iEffect );
  393. WRITE_VEC3COORD( vecOrigin );
  394. WRITE_ANGLES( qAngles );
  395. MessageEnd();
  396. }
  397. //-----------------------------------------------------------------------------
  398. // Purpose: Fizzle the portal
  399. //-----------------------------------------------------------------------------
  400. void CProp_Portal::OnPortalDeactivated( void )
  401. {
  402. if ( m_pAmbientSound )
  403. {
  404. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  405. controller.SoundChangeVolume( m_pAmbientSound, 0.0, 0.0 );
  406. }
  407. //TODO: Fizzle Effects
  408. DoFizzleEffect( m_FizzleEffect );
  409. m_FizzleEffect = PORTAL_FIZZLE_KILLED; //assume we want a generic killed type unless someone sets it to something else before we fizzle next. Lets CPortal_Base2D kill us with a fizzle effect while it has no knowledge of fizzling
  410. BaseClass::OnPortalDeactivated();
  411. }
  412. //-----------------------------------------------------------------------------
  413. // Purpose: Portal will fizzle next time we get to think
  414. //-----------------------------------------------------------------------------
  415. void CProp_Portal::Fizzle( void )
  416. {
  417. m_FizzleEffect = PORTAL_FIZZLE_NONE; //Logic that uses Fizzle() always calls DoFizzleEffect() manually
  418. //DeactivatePortalOnThink();
  419. DeactivatePortalNow();
  420. }
  421. void CProp_Portal::Activate( void )
  422. {
  423. CreateSounds();
  424. BaseClass::Activate();
  425. }
  426. //-----------------------------------------------------------------------------
  427. // Purpose: Kinda sucks... Normal triggers won't find portals because they're also triggers.
  428. // Rather than addressing that directly, portal detectors look for portals with an explicit OBB check.
  429. //
  430. //-----------------------------------------------------------------------------
  431. void CProp_Portal::UpdatePortalDetectorsOnPortalMoved( void )
  432. {
  433. for ( CFuncPortalDetector *pDetector = GetPortalDetectorList(); pDetector != NULL; pDetector = pDetector->m_pNext )
  434. {
  435. pDetector->UpdateOnPortalMoved( this );
  436. }
  437. }
  438. void CProp_Portal::UpdatePortalDetectorsOnPortalActivated( void )
  439. {
  440. for ( CFuncPortalDetector *pDetector = GetPortalDetectorList(); pDetector != NULL; pDetector = pDetector->m_pNext )
  441. {
  442. pDetector->UpdateOnPortalActivated( this );
  443. }
  444. }
  445. void CProp_Portal::UpdatePortalLinkage( void )
  446. {
  447. if( IsActive() )
  448. {
  449. CProp_Portal *pLink = (CProp_Portal *)m_hLinkedPortal.Get();
  450. if( !(pLink && pLink->IsActive()) )
  451. {
  452. //no old link, or inactive old link
  453. if( pLink )
  454. {
  455. //we had an old link, must be inactive. Make doubly sure it's disconnected
  456. if( pLink->m_hLinkedPortal.Get() != NULL )
  457. {
  458. if( pLink->m_hLinkedPortal.Get() == this )
  459. pLink->m_hLinkedPortal = NULL; //avoid recursion
  460. pLink->UpdatePortalLinkage();
  461. }
  462. pLink = NULL;
  463. }
  464. int iPortalCount = s_PortalLinkageGroups[m_iLinkageGroupID].Count();
  465. // More than two sharing a linkage id? is that valid?
  466. //Assert( iPortalCount <3 ); yes it is as long as only two are active
  467. if( iPortalCount != 0 )
  468. {
  469. CProp_Portal **pPortals = s_PortalLinkageGroups[m_iLinkageGroupID].Base();
  470. for( int i = 0; i != iPortalCount; ++i )
  471. {
  472. CProp_Portal *pCurrentPortal = pPortals[i];
  473. if( pCurrentPortal == this )
  474. continue;
  475. if( pCurrentPortal->IsActive() &&
  476. (pCurrentPortal->m_hLinkedPortal.Get() == NULL) &&
  477. (pCurrentPortal->m_fNetworkHalfWidth == m_fNetworkHalfWidth) &&
  478. (pCurrentPortal->m_fNetworkHalfHeight == m_fNetworkHalfHeight) )
  479. {
  480. pLink = pCurrentPortal;
  481. pCurrentPortal->m_hLinkedPortal = this;
  482. pCurrentPortal->UpdatePortalLinkage();
  483. break;
  484. }
  485. }
  486. }
  487. }
  488. m_hLinkedPortal = pLink;
  489. }
  490. else
  491. {
  492. CProp_Portal *pRemote = (CProp_Portal *)m_hLinkedPortal.Get();
  493. //apparently we've been deactivated
  494. m_PortalSimulator.DetachFromLinked();
  495. m_PortalSimulator.ReleaseAllEntityOwnership();
  496. m_hLinkedPortal = NULL;
  497. if( pRemote )
  498. pRemote->UpdatePortalLinkage();
  499. }
  500. BaseClass::UpdatePortalLinkage();
  501. }
  502. void CProp_Portal::DispatchPortalPlacementParticles( bool bIsSecondaryPortal )
  503. {
  504. // never do this in multiplayer
  505. if ( GameRules()->IsMultiplayer() )
  506. return;
  507. // the particle effects are no longer created on the server in SP unless this convar is set,
  508. // if it's not set, they are created on the client in function: CreateAttachedParticles
  509. if ( !use_server_portal_particles.GetBool() )
  510. return;
  511. // Send the particles only to the player who
  512. CBasePlayer *pFiringPlayer = ToBasePlayer( m_hFiredByPlayer.Get() );
  513. if ( pFiringPlayer )
  514. {
  515. CSingleUserRecipientFilter localFilter( pFiringPlayer );
  516. localFilter.MakeReliable();
  517. DispatchParticleEffect( ( ( bIsSecondaryPortal ) ? ( "portal_2_edge" ) : ( "portal_1_edge" ) ), PATTACH_POINT_FOLLOW, this, "particles", true, -1, &localFilter );
  518. }
  519. }
  520. void CProp_Portal::NewLocation( const Vector &vOrigin, const QAngle &qAngles )
  521. {
  522. BaseClass::NewLocation( vOrigin, qAngles );
  523. CreateSounds();
  524. UpdatePortalDetectorsOnPortalMoved();
  525. if( (m_hLinkedPortal.Get() != NULL) && (m_bOldActivatedState == false) && (IsActive() == true) )
  526. {
  527. //went from inactive to active
  528. UpdatePortalDetectorsOnPortalActivated();
  529. ((CProp_Portal *)m_hLinkedPortal.Get())->UpdatePortalDetectorsOnPortalActivated();
  530. }
  531. if( m_NotifyOnPortalled && !m_NotifyOnPortalled->IsPortalTouchingDetector( this ) )
  532. m_NotifyOnPortalled = NULL;
  533. if ( m_pAmbientSound )
  534. {
  535. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  536. controller.SoundChangeVolume( m_pAmbientSound, 0.4, 0.1 );
  537. }
  538. // Place the particles in position
  539. DispatchPortalPlacementParticles( m_bIsPortal2 );
  540. if( !IsMobile() )
  541. {
  542. if ( m_bIsPortal2 )
  543. {
  544. EmitSound( "Portal.open_red" );
  545. }
  546. else
  547. {
  548. EmitSound( "Portal.open_blue" );
  549. }
  550. }
  551. }
  552. void CProp_Portal::PreTeleportTouchingEntity( CBaseEntity *pOther )
  553. {
  554. if( m_NotifyOnPortalled )
  555. m_NotifyOnPortalled->OnPrePortalled( pOther, true );
  556. CProp_Portal *pLinked = (CProp_Portal *)m_hLinkedPortal.Get();
  557. if( pLinked->m_NotifyOnPortalled )
  558. pLinked->m_NotifyOnPortalled->OnPrePortalled( pOther, false );
  559. BaseClass::PreTeleportTouchingEntity( pOther );
  560. }
  561. void CProp_Portal::PostTeleportTouchingEntity( CBaseEntity *pOther )
  562. {
  563. if( m_NotifyOnPortalled )
  564. m_NotifyOnPortalled->OnPostPortalled( pOther, true );
  565. CProp_Portal *pLinked = (CProp_Portal *)m_hLinkedPortal.Get();
  566. if( pLinked->m_NotifyOnPortalled )
  567. pLinked->m_NotifyOnPortalled->OnPostPortalled( pOther, false );
  568. BaseClass::PostTeleportTouchingEntity( pOther );
  569. }
  570. //-----------------------------------------------------------------------------
  571. // Purpose:
  572. //-----------------------------------------------------------------------------
  573. void CProp_Portal::ActivatePortal( void )
  574. {
  575. m_hPlacedBy = NULL;
  576. Vector vOrigin;
  577. vOrigin = GetAbsOrigin();
  578. Vector vForward, vUp;
  579. GetVectors( &vForward, 0, &vUp );
  580. CTraceFilterSimpleClassnameList baseFilter( this, COLLISION_GROUP_NONE );
  581. UTIL_Portal_Trace_Filter( &baseFilter );
  582. CTraceFilterTranslateClones traceFilterPortalShot( &baseFilter );
  583. trace_t tr;
  584. UTIL_TraceLine( vOrigin + vForward, vOrigin + vForward * -8.0f, MASK_SHOT_PORTAL, &traceFilterPortalShot, &tr );
  585. QAngle qAngles;
  586. VectorAngles( tr.plane.normal, vUp, qAngles );
  587. PortalPlacementResult_t eResult = VerifyPortalPlacementAndFizzleBlockingPortals( this, tr.endpos, qAngles, GetHalfWidth(), GetHalfHeight(), PORTAL_PLACED_BY_FIXED );
  588. PlacePortal( tr.endpos, qAngles, eResult );
  589. // If the fixed portal is overlapping a portal that was placed before it... kill it!
  590. if ( PortalPlacementSucceeded( eResult ) )
  591. {
  592. CreateSounds();
  593. if ( m_pAmbientSound )
  594. {
  595. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  596. controller.SoundChangeVolume( m_pAmbientSound, 0.4, 0.1 );
  597. }
  598. // Place the particles in position
  599. DispatchPortalPlacementParticles( m_bIsPortal2 );
  600. if ( m_bIsPortal2 )
  601. {
  602. EmitSound( "Portal.open_red" );
  603. }
  604. else
  605. {
  606. EmitSound( "Portal.open_blue" );
  607. }
  608. }
  609. UpdatePortalTeleportMatrix();
  610. UpdatePortalLinkage();
  611. CBaseProjector::TestAllForProjectionChanges();
  612. }
  613. //-----------------------------------------------------------------------------
  614. // Purpose:
  615. //-----------------------------------------------------------------------------
  616. void CProp_Portal::DeactivatePortal( void )
  617. {
  618. if ( m_pAmbientSound )
  619. {
  620. CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController();
  621. controller.SoundChangeVolume( m_pAmbientSound, 0.0, 0.0 );
  622. }
  623. StopParticleEffects( this );
  624. UpdatePortalTeleportMatrix();
  625. UpdatePortalLinkage();
  626. CBaseProjector::TestAllForProjectionChanges();
  627. }
  628. void CProp_Portal::InputSetActivatedState( inputdata_t &inputdata )
  629. {
  630. SetActive( inputdata.value.Bool() );
  631. if ( IsActive() )
  632. {
  633. ActivatePortal();
  634. }
  635. else
  636. {
  637. DeactivatePortal();
  638. }
  639. }
  640. void CProp_Portal::InputFizzle( inputdata_t &inputdata )
  641. {
  642. DoFizzleEffect( PORTAL_FIZZLE_KILLED, false );
  643. Fizzle();
  644. }
  645. //-----------------------------------------------------------------------------
  646. // Purpose: Map can call new location, so far it's only for debugging purposes so it's not made to be very robust.
  647. // Input : &inputdata - String with 6 float entries with space delimiters, location and orientation
  648. //-----------------------------------------------------------------------------
  649. void CProp_Portal::InputNewLocation( inputdata_t &inputdata )
  650. {
  651. char sLocationStats[MAX_PATH];
  652. Q_strncpy( sLocationStats, inputdata.value.String(), sizeof(sLocationStats) );
  653. // first 3 are location of new origin
  654. Vector vNewOrigin;
  655. char* pTok = strtok( sLocationStats, " " );
  656. vNewOrigin.x = atof(pTok);
  657. pTok = strtok( NULL, " " );
  658. vNewOrigin.y = atof(pTok);
  659. pTok = strtok( NULL, " " );
  660. vNewOrigin.z = atof(pTok);
  661. // Next 3 entries are new angles
  662. QAngle vNewAngles;
  663. pTok = strtok( NULL, " " );
  664. vNewAngles.x = atof(pTok);
  665. pTok = strtok( NULL, " " );
  666. vNewAngles.y = atof(pTok);
  667. pTok = strtok( NULL, " " );
  668. vNewAngles.z = atof(pTok);
  669. // Call main placement function (skipping placement rules)
  670. NewLocation( vNewOrigin, vNewAngles );
  671. }
  672. void CProp_Portal::InputResize( inputdata_t &inputdata )
  673. {
  674. char sResizeStats[MAX_PATH];
  675. Q_strncpy( sResizeStats, inputdata.value.String(), sizeof(sResizeStats) );
  676. char* pTok = strtok( sResizeStats, " " );
  677. float fHalfWidth = atof(pTok);
  678. pTok = strtok( NULL, " " );
  679. float fHalfHeight = atof(pTok);
  680. Resize( fHalfWidth, fHalfHeight );
  681. }
  682. void CProp_Portal::InputSetLinkageGroupId( inputdata_t &inputdata )
  683. {
  684. int iGroupId = inputdata.value.Int();
  685. if ( ( iGroupId >= 0 ) && ( iGroupId < 255 ) )
  686. {
  687. ChangeLinkageGroup( iGroupId );
  688. if ( IsActive() )
  689. {
  690. SetActive( false );
  691. // shut the portal down and reactivate it so it will re-link with new portal group id
  692. DeactivatePortal();
  693. SetActive( true );
  694. ActivatePortal();
  695. }
  696. }
  697. else
  698. {
  699. Warning( "*** SetLinkageGroupId input failed because Portal ID must be between 0 and 255!\n" );
  700. }
  701. }
  702. void CProp_Portal::AddToLinkageGroup( void )
  703. {
  704. if ( m_iLinkageGroupID != PORTAL_LINKAGE_GROUP_INVALID )
  705. {
  706. if( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) == -1 )
  707. s_PortalLinkageGroups[m_iLinkageGroupID].AddToTail( this );
  708. }
  709. }
  710. void CProp_Portal::ChangeLinkageGroup( unsigned char iLinkageGroupID )
  711. {
  712. if ( iLinkageGroupID == PORTAL_LINKAGE_GROUP_INVALID )
  713. {
  714. // invalid is the 'inactive portal' group for portals not yet linked.
  715. m_iLinkageGroupID = iLinkageGroupID;
  716. return;
  717. }
  718. // We should be moving from a linkage id to another one, unles we're coming from INVALID
  719. Assert( s_PortalLinkageGroups[m_iLinkageGroupID].Find( this ) != -1 || m_iLinkageGroupID == PORTAL_LINKAGE_GROUP_INVALID );
  720. s_PortalLinkageGroups[m_iLinkageGroupID].FindAndRemove( this );
  721. s_PortalLinkageGroups[iLinkageGroupID].AddToTail( this );
  722. m_iLinkageGroupID = iLinkageGroupID;
  723. }
  724. CProp_Portal *CProp_Portal::FindPortal( unsigned char iLinkageGroupID, bool bPortal2, bool bCreateIfNothingFound /*= false*/ )
  725. {
  726. int iPortalCount = s_PortalLinkageGroups[iLinkageGroupID].Count();
  727. if( iPortalCount != 0 )
  728. {
  729. CProp_Portal *pFoundInactive = NULL;
  730. CProp_Portal **pPortals = s_PortalLinkageGroups[iLinkageGroupID].Base();
  731. for( int i = 0; i != iPortalCount; ++i )
  732. {
  733. if( pPortals[i]->m_bIsPortal2 == bPortal2 )
  734. {
  735. if( pPortals[i]->IsActive() )
  736. return pPortals[i];
  737. else
  738. pFoundInactive = pPortals[i];
  739. }
  740. }
  741. if( pFoundInactive )
  742. return pFoundInactive;
  743. }
  744. if( bCreateIfNothingFound )
  745. {
  746. CProp_Portal *pPortal = (CProp_Portal *)CreateEntityByName( "prop_portal" );
  747. pPortal->m_iLinkageGroupID = iLinkageGroupID;
  748. pPortal->m_bIsPortal2 = bPortal2;
  749. DispatchSpawn( pPortal );
  750. return pPortal;
  751. }
  752. return NULL;
  753. }
  754. const CUtlVector<CProp_Portal *> *CProp_Portal::GetPortalLinkageGroup( unsigned char iLinkageGroupID )
  755. {
  756. return &s_PortalLinkageGroups[iLinkageGroupID];
  757. }
  758. // Hands out linkage IDs in order. If somebody has taken the slot, it walks to a free one and picks that as the new starting location.
  759. static unsigned char s_iBestGuessUnusedLinkageID = 0;
  760. unsigned char UTIL_GetUnusedLinkageID( void )
  761. {
  762. if ( s_PortalLinkageGroups[s_iBestGuessUnusedLinkageID].Count() == 0 )
  763. {
  764. // early out for best guess
  765. return s_iBestGuessUnusedLinkageID++;
  766. }
  767. else
  768. {
  769. // walk all linkage groups for a free one
  770. for ( int i = 0; i < 256; ++i )
  771. {
  772. if ( s_PortalLinkageGroups[i].Count() == 0 )
  773. {
  774. s_iBestGuessUnusedLinkageID = i+1;
  775. return i;
  776. }
  777. }
  778. }
  779. Warning( "*** All portal linkage IDs in use! ***\nThere may be >254 portal pairs, or some bug causing the linkage IDs not to be freed up.\n" );
  780. Assert( 0 );
  781. return PORTAL_LINKAGE_GROUP_INVALID;
  782. }
  783. //------------------------------------------------------------------------------
  784. // Purpose: Create an NPC of the given type
  785. //------------------------------------------------------------------------------
  786. void CC_Resize_Portals( const CCommand &args )
  787. {
  788. if( args.ArgC() < 3 )
  789. {
  790. Warning( "syntax: Portals_ResizeAll [half width] [half height]\n" );
  791. return;
  792. }
  793. float fHalfWidth = atof(args[1]);
  794. float fHalfHeight = atof(args[2]);
  795. int iPortalCount = CProp_Portal_Shared::AllPortals.Count();
  796. for( int i = 0; i != iPortalCount; ++i )
  797. {
  798. CProp_Portal_Shared::AllPortals[i]->Resize( fHalfWidth, fHalfHeight );
  799. }
  800. CProp_Portal::ms_DefaultPortalHalfWidth = fHalfWidth;
  801. CProp_Portal::ms_DefaultPortalHalfHeight = fHalfHeight;
  802. }
  803. static ConCommand Portals_ResizeAll("Portals_ResizeAll", CC_Resize_Portals, "Resizes all portals (for testing), Portals_ResizeAll [half width] [half height]", FCVAR_CHEAT);