//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: Rising liquid that acts as a one-way portal // // $NoKeywords: $ //===========================================================================// #include "cbase.h" #include "func_liquidportal.h" #include "portal_player.h" #include "isaverestore.h" #include "saverestore_utlvector.h" LINK_ENTITY_TO_CLASS( func_liquidportal, CFunc_LiquidPortal ); BEGIN_DATADESC( CFunc_LiquidPortal ) DEFINE_FIELD( m_hLinkedPortal, FIELD_EHANDLE ), DEFINE_FIELD( m_bFillInProgress, FIELD_BOOLEAN ), DEFINE_FIELD( m_fFillStartTime, FIELD_TIME ), DEFINE_FIELD( m_fFillEndTime, FIELD_TIME ), DEFINE_FIELD( m_matrixThisToLinked, FIELD_VMATRIX ), DEFINE_UTLVECTOR( m_hTeleportList, FIELD_EHANDLE ), DEFINE_UTLVECTOR( m_hLeftToTeleportThisFill, FIELD_EHANDLE ), DEFINE_KEYFIELD( m_strInitialLinkedPortal, FIELD_STRING, "InitialLinkedPortal" ), DEFINE_KEYFIELD( m_fFillTime, FIELD_FLOAT, "FillTime" ), // Inputs DEFINE_INPUTFUNC( FIELD_STRING, "SetLinkedLiquidPortal", InputSetLinkedLiquidPortal ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetFillTime", InputSetFillTime ), DEFINE_INPUTFUNC( FIELD_VOID, "StartFilling", InputStartFilling ), DEFINE_INPUTFUNC( FIELD_VOID, "AddActivatorToTeleportList", InputAddActivatorToTeleportList ), DEFINE_INPUTFUNC( FIELD_VOID, "RemoveActivatorFromTeleportList", InputRemoveActivatorFromTeleportList ), DEFINE_FUNCTION( CBaseEntity::Think ), END_DATADESC() IMPLEMENT_SERVERCLASS_ST( CFunc_LiquidPortal, DT_Func_LiquidPortal ) SendPropEHandle( SENDINFO(m_hLinkedPortal) ), SendPropFloat( SENDINFO(m_fFillStartTime) ), SendPropFloat( SENDINFO(m_fFillEndTime) ), END_SEND_TABLE() CFunc_LiquidPortal::CFunc_LiquidPortal( void ) : m_bFillInProgress( false ) { m_matrixThisToLinked.Identity(); //Zero space is a bad place. No heroes to face, but we need 1's in this case. } void CFunc_LiquidPortal::Spawn( void ) { BaseClass::Spawn(); SetSolid( SOLID_VPHYSICS ); SetSolidFlags( FSOLID_NOT_SOLID ); SetMoveType( MOVETYPE_NONE ); SetModel( STRING( GetModelName() ) ); CBaseEntity *pBaseEnt = gEntList.FindEntityByName( NULL, STRING(m_strInitialLinkedPortal) ); Assert( (pBaseEnt == NULL) || (dynamic_cast(pBaseEnt) != NULL) ); SetLinkedLiquidPortal( (CFunc_LiquidPortal *)pBaseEnt ); SetThink( &CFunc_LiquidPortal::Think ); } void CFunc_LiquidPortal::Activate( void ) { BaseClass::Activate(); SetSolid( SOLID_VPHYSICS ); SetSolidFlags( FSOLID_NOT_SOLID ); SetMoveType( MOVETYPE_NONE ); SetModel( STRING( GetModelName() ) ); ComputeLinkMatrix(); //collision origin may have changed during activation SetThink( &CFunc_LiquidPortal::Think ); for( int i = m_hLeftToTeleportThisFill.Count(); --i >= 0; ) { CBaseEntity *pEnt = m_hLeftToTeleportThisFill[i].Get(); if( pEnt && pEnt->IsPlayer() ) { ((CPortal_Player *)pEnt)->m_hSurroundingLiquidPortal = this; } } } int CFunc_LiquidPortal::Save( ISave &save ) { if( !BaseClass::Save( save ) ) return 0; save.StartBlock( "LiquidPortal" ); short iTeleportListCount = m_hTeleportList.Count(); save.WriteShort( &iTeleportListCount ); if( iTeleportListCount != 0 ) save.WriteEHandle( m_hTeleportList.Base(), iTeleportListCount ); short iLeftToTeleportThisFillCount = m_hLeftToTeleportThisFill.Count(); save.WriteShort( &iLeftToTeleportThisFillCount ); if( iLeftToTeleportThisFillCount != 0 ) save.WriteEHandle( m_hLeftToTeleportThisFill.Base(), iLeftToTeleportThisFillCount ); save.EndBlock(); return 1; } int CFunc_LiquidPortal::Restore( IRestore &restore ) { m_hTeleportList.RemoveAll(); m_hLeftToTeleportThisFill.RemoveAll(); if( !BaseClass::Restore( restore ) ) return 0; char szBlockName[SIZE_BLOCK_NAME_BUF]; restore.StartBlock( szBlockName ); if( !FStrEq( szBlockName, "LiquidPortal" ) ) //loading a save without liquid portal save data return 1; short iTeleportListCount; restore.ReadShort( &iTeleportListCount ); if( iTeleportListCount != 0 ) { m_hTeleportList.SetCount( iTeleportListCount ); restore.ReadEHandle( m_hTeleportList.Base(), iTeleportListCount ); } short iLeftToTeleportThisFillCount; restore.ReadShort( &iLeftToTeleportThisFillCount ); if( iLeftToTeleportThisFillCount != 0 ) { m_hLeftToTeleportThisFill.SetCount( iLeftToTeleportThisFillCount ); restore.ReadEHandle( m_hLeftToTeleportThisFill.Base(), iLeftToTeleportThisFillCount ); } restore.EndBlock(); return 1; } void CFunc_LiquidPortal::InputSetLinkedLiquidPortal( inputdata_t &inputdata ) { CBaseEntity *pBaseEnt = gEntList.FindEntityByName( NULL, inputdata.value.String() ); Assert( (pBaseEnt == NULL) || (dynamic_cast(pBaseEnt) != NULL) ); SetLinkedLiquidPortal( (CFunc_LiquidPortal *)pBaseEnt ); } void CFunc_LiquidPortal::InputSetFillTime( inputdata_t &inputdata ) { m_fFillTime = inputdata.value.Float(); } void CFunc_LiquidPortal::InputStartFilling( inputdata_t &inputdata ) { AssertMsg( m_fFillEndTime <= gpGlobals->curtime, "Fill already in progress." ); m_fFillStartTime = gpGlobals->curtime; m_fFillEndTime = gpGlobals->curtime + m_fFillTime; m_bFillInProgress = true; //reset the teleport list for this fill m_hLeftToTeleportThisFill.RemoveAll(); m_hLeftToTeleportThisFill.AddVectorToTail( m_hTeleportList ); SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); } void CFunc_LiquidPortal::InputAddActivatorToTeleportList( inputdata_t &inputdata ) { if( inputdata.pActivator == NULL ) return; for( int i = m_hTeleportList.Count(); --i >= 0; ) { if( m_hTeleportList[i].Get() == inputdata.pActivator ) return; //only have 1 reference of each entity } m_hTeleportList.AddToTail( inputdata.pActivator ); if( m_bFillInProgress ) m_hLeftToTeleportThisFill.AddToTail( inputdata.pActivator ); if( inputdata.pActivator->IsPlayer() ) ((CPortal_Player *)inputdata.pActivator)->m_hSurroundingLiquidPortal = this; } void CFunc_LiquidPortal::InputRemoveActivatorFromTeleportList( inputdata_t &inputdata ) { if( inputdata.pActivator == NULL ) return; for( int i = m_hTeleportList.Count(); --i >= 0; ) { if( m_hTeleportList[i].Get() == inputdata.pActivator ) { m_hTeleportList.FastRemove( i ); if( inputdata.pActivator->IsPlayer() && (((CPortal_Player *)inputdata.pActivator)->m_hSurroundingLiquidPortal.Get() == this) ) ((CPortal_Player *)inputdata.pActivator)->m_hSurroundingLiquidPortal = NULL; if( m_bFillInProgress ) { //remove from the list for this fill as well for( int j = m_hLeftToTeleportThisFill.Count(); --j >= 0; ) { if( m_hLeftToTeleportThisFill[j].Get() == inputdata.pActivator ) { m_hLeftToTeleportThisFill.FastRemove( j ); break; } } } return; } } } void CFunc_LiquidPortal::SetLinkedLiquidPortal( CFunc_LiquidPortal *pLinked ) { CFunc_LiquidPortal *pCurrentLinkedPortal = m_hLinkedPortal.Get(); if( pCurrentLinkedPortal == pLinked ) return; if( pCurrentLinkedPortal != NULL ) { m_hLinkedPortal = NULL; pCurrentLinkedPortal->SetLinkedLiquidPortal( NULL ); } m_hLinkedPortal = pLinked; if( pLinked != NULL ) pLinked->SetLinkedLiquidPortal( this ); ComputeLinkMatrix(); } void CFunc_LiquidPortal::ComputeLinkMatrix( void ) { CFunc_LiquidPortal *pLinkedPortal = m_hLinkedPortal.Get(); if( pLinkedPortal ) { VMatrix matLocalToWorld, matLocalToWorldInv, matRemoteToWorld; matLocalToWorld = EntityToWorldTransform(); matRemoteToWorld = pLinkedPortal->EntityToWorldTransform(); MatrixInverseTR( matLocalToWorld, matLocalToWorldInv ); m_matrixThisToLinked = matRemoteToWorld * matLocalToWorldInv; MatrixInverseTR( m_matrixThisToLinked, pLinkedPortal->m_matrixThisToLinked ); } else { m_matrixThisToLinked.Identity(); } } void CFunc_LiquidPortal::TeleportImmersedEntity( CBaseEntity *pEntity ) { if( pEntity == NULL ) return; if( pEntity->IsPlayer() ) { CPortal_Player *pEntityAsPlayer = (CPortal_Player *)pEntity; Vector vNewOrigin = m_matrixThisToLinked * pEntity->GetAbsOrigin(); QAngle qNewAngles = TransformAnglesToWorldSpace( pEntityAsPlayer->EyeAngles(), m_matrixThisToLinked.As3x4() ); Vector vNewVelocity = m_matrixThisToLinked.ApplyRotation( pEntity->GetAbsVelocity() ); pEntity->Teleport( &vNewOrigin, &qNewAngles, &vNewVelocity ); pEntityAsPlayer->m_hSurroundingLiquidPortal = m_hLinkedPortal; } else { Vector vNewOrigin = m_matrixThisToLinked * pEntity->GetAbsOrigin(); QAngle qNewAngles = TransformAnglesToWorldSpace( pEntity->GetAbsAngles(), m_matrixThisToLinked.As3x4() ); Vector vNewVelocity = m_matrixThisToLinked.ApplyRotation( pEntity->GetAbsVelocity() ); pEntity->Teleport( &vNewOrigin, &qNewAngles, &vNewVelocity ); } } void CFunc_LiquidPortal::Think( void ) { if( m_bFillInProgress ) { if( gpGlobals->curtime < m_fFillEndTime ) { float fInterp = ((gpGlobals->curtime - m_fFillStartTime) / (m_fFillEndTime - m_fFillStartTime)); Vector vMins, vMaxs; GetCollideable()->WorldSpaceSurroundingBounds( &vMins, &vMaxs ); vMaxs.z = vMins.z + ((vMaxs.z - vMins.z) * fInterp); for( int i = m_hLeftToTeleportThisFill.Count(); --i >= 0; ) { CBaseEntity *pEntity = m_hLeftToTeleportThisFill[i].Get(); if( pEntity == NULL ) continue; Vector vEntMins, vEntMaxs; pEntity->GetCollideable()->WorldSpaceSurroundingBounds( &vEntMins, &vEntMaxs ); if( vEntMaxs.z <= vMaxs.z ) { TeleportImmersedEntity( pEntity ); m_hLeftToTeleportThisFill.FastRemove( i ); } } SetNextThink( gpGlobals->curtime + TICK_INTERVAL ); } else { //teleport everything that's left in the list for( int i = m_hLeftToTeleportThisFill.Count(); --i >= 0; ) { TeleportImmersedEntity( m_hLeftToTeleportThisFill[i].Get() ); } m_hLeftToTeleportThisFill.RemoveAll(); m_bFillInProgress = false; } } }