CAreaPortalOneWay: Difference between revisions
Jump to navigation
Jump to search
Tip:If the player needs to walk through the portal, func_areaportalwindow is a better choice.
Note:You must to grant access to
TomEdwards (talk | contribs) (some entity source code) |
SirYodaJedi (talk | contribs) m (→FGD) |
||
(7 intermediate revisions by one other user not shown) | |||
Line 1: | Line 1: | ||
This is | {{toc-right}} | ||
This is source code for '''<code>CAreaPortalOneWay</code>''', 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|If the player needs to walk ''through'' the portal, [[func_areaportalwindow]] is a better choice.}} | {{tip|If the player needs to walk ''through'' the portal, [[func_areaportalwindow]] is a better choice.}} | ||
== C++ == | == 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 <code>CFuncAreaPortalBase::m_AreaPortalsElement</code> and <code>CAreaPortal::m_state</code> | |||
; June 9 2008 | |||
: ''Initial release.'' | |||
== C++ code == | |||
Paste this at the end of <code>\game\server\func_areaportal.cpp</code> (as there is no header file to #include). | |||
{{note|You must to grant access to <code>CFuncAreaPortalBase::m_AreaPortalsElement</code> and <code>CAreaPortal::m_state</code> before this code will compile. The best way to do so is to move both from <code>private</code> access to <code>protected</code>.}} | |||
<source lang=cpp>// 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; | |||
} | |||
}</source> | |||
=== Doors === | === Doors === | ||
If you want to attach a door to a <code>CAreaPortalOneWay</code>, you will need to modify | If you want to attach a prop or func door to a <code>CAreaPortalOneWay</code>, you will need to modify the doors' code so that they search for <code>func_areaportal*</code> (with a wildcard). | ||
''CBasePropDoor::UpdateAreaPortals, CBaseDoor::UpdateAreaPortals:'' | |||
- <span style="color: red">while ((pPortal = gEntList.FindEntityByClassname(pPortal, "func_areaportal")) != NULL)</span> | |||
+ <span style="color: green">while ((pPortal = gEntList.FindEntityByClassname(pPortal, "func_areaportal*")) != NULL)</span> | |||
== FGD == | == FGD == | ||
<source lang=cpp>@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." | |||
]</source> | |||
== See also == | == See also == | ||
Line 91: | Line 249: | ||
*[[Areaportal]] | *[[Areaportal]] | ||
[[Category: | [[Category:Free source code|A]] |
Latest revision as of 13:53, 30 March 2025
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.

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
andCAreaPortal::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).

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."
]