Moderator elections are being held. See Valve Developer Community:Moderator elections for more details.
Users who would like to run for moderator must be autoconfirmed and have at least 100 edits. Users can check their own edit count at Special:Preferences.
The Message template has been deleted. A list of pages that transclude it are at Valve Developer Community:Message transclusions.

CAreaPortalOneWay

From Valve Developer Community
Jump to: navigation, search

This is source code for CAreaPortalOneWay, an areaportal that is only open when the player is a certain side of its plane. It is useful in brightly-lit windows that can be seen out of but not into.

Tip.pngTip:If the player needs to walk through the portal, func_areaportalwindow is a better choice.

Changes

June 11 2009
Complete rewrite. Any maps using the earlier version of the entity must be re-compiled.
  • Support open/closed state
  • One-way functionality can be toggled
  • One-way direction can be inverted
  • Can be grouped by mapper to share a single state with other portals, reducing calculations
  • Can be set to avoid 'latency pop' when crossing plane
  • Requires access to private members CFuncAreaPortalBase::m_AreaPortalsElement and CAreaPortal::m_state
June 9 2008
Initial release.

C++ code

Paste this at the end of \game\server\func_areaportal.cpp (as there is no header file to #include).

Note.pngNote:You must to grant access to CFuncAreaPortalBase::m_AreaPortalsElement and CAreaPortal::m_state before this code will compile. The best way to do so is to move both from private access to protected.
// An areaportal that automatically closes and opens depending on the direction of the client.
// http://developer.valvesoftware.com/wiki/CAreaPortalOneWay
class CAreaPortalOneWay : public CAreaPortal // CAPOW!
{
	DECLARE_CLASS( CAreaPortalOneWay, CAreaPortal );
	DECLARE_DATADESC();

public:
	Vector	 m_vecOpenVector;
	bool	 m_bAvoidPop;
	bool	 m_bOneWayActive;
	
	void Spawn();
	void Activate();
	int  Restore(IRestore &restore);
	bool UpdateVisibility( const Vector &vOrigin, float fovDistanceAdjustFactor, bool &bIsOpenOnClient );

	void InputDisableOneWay( inputdata_t &inputdata );
	void InputEnableOneWay( inputdata_t &inputdata );
	void InputToggleOneWay( inputdata_t &inputdata );
	void InputInvertOneWay( inputdata_t &inputdata );

protected:
	void RemoteUpdate( bool IsOpen );

	bool m_bRemotelyUpdated;
	bool m_bRemoteCalcWasOpen;
	CHandle<CAreaPortalOneWay> m_hNextPortal; // This get saved to disc...
	CAreaPortalOneWay* m_pNextPortal; // ...while this gets used at runtime, avoiding loads of casts

private:
	void UpdateNextPortal( bool IsOpen );

	// These two are irrelevant once the entity has established itself
	string_t m_strGroupName;
	Vector	 m_vecOrigin_; // The portal won't compile properly if vecOrigin itself has a value, but it's okay to move something in at runtime
};

LINK_ENTITY_TO_CLASS( func_areaportal_oneway, CAreaPortalOneWay );

BEGIN_DATADESC( CAreaPortalOneWay )
	DEFINE_KEYFIELD( m_vecOpenVector, FIELD_VECTOR, "onewayfacing" ),
	DEFINE_KEYFIELD( m_bAvoidPop, FIELD_BOOLEAN, "avoidpop" ),
	DEFINE_KEYFIELD_NOT_SAVED( m_vecOrigin_, FIELD_VECTOR, "origin_" ),
	DEFINE_KEYFIELD_NOT_SAVED( m_strGroupName, FIELD_STRING, "group" ),
	DEFINE_FIELD( m_bOneWayActive, FIELD_BOOLEAN ),
	DEFINE_FIELD( m_hNextPortal, FIELD_EHANDLE ),
	DEFINE_INPUTFUNC( FIELD_VOID, "DisableOneWay", InputDisableOneWay ),
	DEFINE_INPUTFUNC( FIELD_VOID, "EnableOneWay", InputEnableOneWay ),
	DEFINE_INPUTFUNC( FIELD_VOID, "ToggleOneWay", InputToggleOneWay ),
	DEFINE_INPUTFUNC( FIELD_VOID, "InvertOneWay", InputInvertOneWay ),
END_DATADESC()

void CAreaPortalOneWay::Spawn()
{
	// Convert our angle from Hammer to a proper vector
	QAngle angOpenDir = QAngle( m_vecOpenVector.x, m_vecOpenVector.y, m_vecOpenVector.z );
	AngleVectors( angOpenDir, &m_vecOpenVector );

	SetLocalOrigin(m_vecOrigin_);
	m_bOneWayActive = true;
	m_bRemotelyUpdated = false;

	BaseClass::Spawn();
}

void CAreaPortalOneWay::Activate()
{
	// Optimisation: share open/closed value for CAPOWs with the same GroupName.
	if (m_strGroupName != NULL_STRING)
		// m_AreaPortalsElement is a private member of CFuncAreaPortalBase. If this can't be changed, 
		// replace with g_AreaPortals.Find(this). DEFINITELY DO NOT use m_portalNumber!
		for( unsigned short i = m_AreaPortalsElement; i != g_AreaPortals.InvalidIndex(); i = g_AreaPortals.Next(i) )
		{
			CAreaPortalOneWay* pCur = dynamic_cast<CAreaPortalOneWay*>(g_AreaPortals[i]);

			if ( pCur && pCur != this && strcmp( STRING(m_strGroupName),STRING(pCur->m_strGroupName) ) == 0 )
			{
				m_pNextPortal = pCur;
				m_hNextPortal = pCur;
				break;
			}
		}

	BaseClass::Activate();
}

int CAreaPortalOneWay::Restore(IRestore &restore)
{
	if ( m_hNextPortal.IsValid() )
		m_pNextPortal = m_hNextPortal.Get();

	return BaseClass::Restore(restore);
}

// Disable the CAPOW (becomes a normal AP)
void CAreaPortalOneWay::InputDisableOneWay( inputdata_t &inputdata )
{
	m_bOneWayActive = false;
}

// Re-enable the CAPOW
void CAreaPortalOneWay::InputEnableOneWay( inputdata_t &inputdata )
{
	m_bOneWayActive = true;
}

// Toggle CAPOW
void CAreaPortalOneWay::InputToggleOneWay( inputdata_t &inputdata )
{
	m_bOneWayActive = !m_bOneWayActive;
}

// Flip the one way direction
void CAreaPortalOneWay::InputInvertOneWay( inputdata_t &inputdata )
{
	m_vecOpenVector.Negate();
}

// Recieve a shared state from another CAPOW, then pass it on to the next
void CAreaPortalOneWay::RemoteUpdate( bool IsOpen )
{
	m_bRemotelyUpdated = true;
	m_bRemoteCalcWasOpen = IsOpen;
	UpdateNextPortal(IsOpen);
}

// Inline func since this code is required three times
inline void CAreaPortalOneWay::UpdateNextPortal( bool IsOpen )
{
	if (m_pNextPortal)
		m_pNextPortal->RemoteUpdate(IsOpen);
}

#define VIEWER_PADDING 80 // Value copied from func_areaportalbase.cpp

bool CAreaPortalOneWay::UpdateVisibility( const Vector &vOrigin, float fovDistanceAdjustFactor, bool &bIsOpenOnClient )
{
	if (!m_bOneWayActive)
		return BaseClass::UpdateVisibility( vOrigin, fovDistanceAdjustFactor, bIsOpenOnClient );

	if( m_portalNumber == -1 || m_state == AREAPORTAL_CLOSED ) // m_state is a private member of CAreaPortal by default
	{
		bIsOpenOnClient = false;
		return false;
	}

	// Has another CAPOW on our plane already done a calculation?
	// Note that the CAPOW chain is traversed with new values in RemoteUpdate(), NOT here
	if (m_bRemotelyUpdated)
	{
		m_bRemotelyUpdated = false;
		return m_bRemoteCalcWasOpen ? BaseClass::UpdateVisibility( vOrigin, fovDistanceAdjustFactor, bIsOpenOnClient ) : false;
	}

	// ***********************
	// If we've got this far then we're the first CAPOW in the chain this frame
	// and need to calculate a value and pass it along said chain ourselves
	// ***********************

	float dist = VIEWER_PADDING; // Assume open for backfacing tests...
	VPlane plane;
	if( engine->GetAreaPortalPlane(vOrigin,m_portalNumber,&plane) )
		dist = plane.DistTo(vOrigin);	// ...but if we find a plane, use a genuine figure instead.
										// This is done because GetAreaPortalPlane only works for 
										// portals facing the current area.

	// We can use LocalOrigin here because APs never have parents.
	float dot = DotProduct(m_vecOpenVector,vOrigin - GetLocalOrigin());

	if( dot > 0 )
	{
		// We are on the open side of the portal. Pass the result on!
		UpdateNextPortal(true);
		
		// The following backfacing check is the inverse of CFuncAreaPortalBase's: 
		// it /closes/ the portal if the camera is /behind/ the plane. IsOpenOnClient
		// is left alone as per func_areaportalbase.h
		return dist < -VIEWER_PADDING ? false : true;
	}
	else // Closed side
	{
		// To avoid latency pop when crossing the portal's plane, it is only 
		// closed on the client if said client is outside the "padding zone".
		if ( !m_bAvoidPop || (m_bAvoidPop && dist > VIEWER_PADDING) )
			bIsOpenOnClient = false;

		// We are definitely closed on the server, however.
		UpdateNextPortal(false);
		return false;
	}
}

Doors

If you want to attach a prop or func door to a CAreaPortalOneWay, you will need to modify the doors' code so that they search for func_areaportal* (with a wildcard).

CBasePropDoor::UpdateAreaPortals, CBaseDoor::UpdateAreaPortals:
  - while ((pPortal = gEntList.FindEntityByClassname(pPortal, "func_areaportal")) != NULL)
  + while ((pPortal = gEntList.FindEntityByClassname(pPortal, "func_areaportal*")) != NULL)

FGD

@SolidClass base(func_areaportal) color(0 255 255) = func_areaportal_oneway :	"An areaportal that is only open when viewed from one direction."
[
	origin_(origin) readonly : "Origin" : : "Point from which the areaportal's location is determined (they are a special case and cannot use the normal value). Read-only."
	group(string) : "One-way group" : : "Optimisation: oneway portals in the same group share a single closed/open state. Use this, for example, on walls full of one-way windows."
	onewayfacing(angle) : "Open direction" : "0 0 0" : "The portal will be open when the player is within 90 degrees of this direction."
	avoidpop(choices) : "Avoid latency pop" : 0 : "Enable this if it becomes noticeable that the portal stays closed momentarily after the player walks past it. The portal will open 80 units in advance." =
	[
		0 : "No"
		1 : "Yes"
	]
	
	input DisableOneWay(void) : "Disable the one-way behaviour of the portal."
	input EnableOneWay(void) : "Enable the one-way behaviour of the portal."
	input ToggleOneWay(void) : "Toggle the one-way behaviour of the portal."
	input InvertOneWay(void) : "Flip the one-way direction."
]

See also