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.
562 lines
16 KiB
562 lines
16 KiB
//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Used to fire events based on the orientation of a given entity. |
|
// |
|
// Looks at its target's angles every frame and fires an output if its |
|
// target's forward vector points at a specified lookat entity for more |
|
// than a specified length of time. |
|
// |
|
// It also fires an output whenever the target's angles change. |
|
// |
|
//=============================================================================// |
|
|
|
#include "cbase.h" |
|
#include "entityinput.h" |
|
#include "entityoutput.h" |
|
#include "eventqueue.h" |
|
#include "mathlib/mathlib.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
#define SF_USE_TARGET_FACING (1<<0) // Use the target entity's direction instead of position |
|
|
|
class CPointAngleSensor : public CPointEntity |
|
{ |
|
DECLARE_CLASS(CPointAngleSensor, CPointEntity); |
|
public: |
|
|
|
bool KeyValue(const char *szKeyName, const char *szValue); |
|
void Activate(void); |
|
void Spawn(void); |
|
void Think(void); |
|
|
|
int DrawDebugTextOverlays(void); |
|
|
|
protected: |
|
|
|
void Enable(); |
|
void Disable(); |
|
|
|
// Input handlers |
|
void InputEnable(inputdata_t &inputdata); |
|
void InputDisable(inputdata_t &inputdata); |
|
void InputToggle(inputdata_t &inputdata); |
|
void InputTest(inputdata_t &inputdata); |
|
void InputSetTargetEntity(inputdata_t &inputdata); |
|
|
|
bool IsFacingWithinTolerance(CBaseEntity *pEntity, CBaseEntity *pTarget, float flTolerance, float *pflDot = NULL); |
|
|
|
bool m_bDisabled; // When disabled, we do not think or fire outputs. |
|
string_t m_nLookAtName; // Name of the entity that the target must point at to fire the OnTrue output. |
|
|
|
EHANDLE m_hTargetEntity; // Entity whose angles are being monitored. |
|
EHANDLE m_hLookAtEntity; // Entity that the target must look at to fire the OnTrue output. |
|
|
|
float m_flDuration; // Time in seconds for which the entity must point at the target. |
|
float m_flDotTolerance; // Degrees of error allowed to satisfy the condition, expressed as a dot product. |
|
float m_flFacingTime; // The time at which the target entity pointed at the lookat entity. |
|
bool m_bFired; // Latches the output so it only fires once per true. |
|
|
|
// Outputs |
|
COutputEvent m_OnFacingLookat; // Fired when the target points at the lookat entity. |
|
COutputEvent m_OnNotFacingLookat; // Fired in response to a Test input if the target is not looking at the lookat entity. |
|
COutputVector m_TargetDir; |
|
COutputFloat m_FacingPercentage; // Normalize value representing how close the entity is to facing directly at the target |
|
|
|
DECLARE_DATADESC(); |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS(point_anglesensor, CPointAngleSensor); |
|
|
|
|
|
BEGIN_DATADESC(CPointAngleSensor) |
|
|
|
// Keys |
|
DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled"), |
|
DEFINE_KEYFIELD(m_nLookAtName, FIELD_STRING, "lookatname"), |
|
DEFINE_FIELD(m_hTargetEntity, FIELD_EHANDLE), |
|
DEFINE_FIELD(m_hLookAtEntity, FIELD_EHANDLE), |
|
DEFINE_KEYFIELD(m_flDuration, FIELD_FLOAT, "duration"), |
|
DEFINE_FIELD(m_flDotTolerance, FIELD_FLOAT), |
|
DEFINE_FIELD(m_flFacingTime, FIELD_TIME), |
|
DEFINE_FIELD(m_bFired, FIELD_BOOLEAN), |
|
|
|
// Outputs |
|
DEFINE_OUTPUT(m_OnFacingLookat, "OnFacingLookat"), |
|
DEFINE_OUTPUT(m_OnNotFacingLookat, "OnNotFacingLookat"), |
|
DEFINE_OUTPUT(m_TargetDir, "TargetDir"), |
|
DEFINE_OUTPUT(m_FacingPercentage, "FacingPercentage"), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), |
|
DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), |
|
DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle), |
|
DEFINE_INPUTFUNC(FIELD_VOID, "Test", InputTest), |
|
DEFINE_INPUTFUNC(FIELD_STRING, "SetTargetEntity", InputSetTargetEntity), |
|
|
|
END_DATADESC() |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handles keyvalues that require special processing. |
|
// Output : Returns true if handled, false if not. |
|
//----------------------------------------------------------------------------- |
|
bool CPointAngleSensor::KeyValue(const char *szKeyName, const char *szValue) |
|
{ |
|
if (FStrEq(szKeyName, "tolerance")) |
|
{ |
|
float flTolerance = atof(szValue); |
|
m_flDotTolerance = cos(DEG2RAD(flTolerance)); |
|
} |
|
else |
|
{ |
|
return(BaseClass::KeyValue(szKeyName, szValue)); |
|
} |
|
|
|
return(true); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called when spawning after parsing keyvalues. |
|
//----------------------------------------------------------------------------- |
|
void CPointAngleSensor::Spawn(void) |
|
{ |
|
BaseClass::Spawn(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called after all entities have spawned on new map or savegame load. |
|
//----------------------------------------------------------------------------- |
|
void CPointAngleSensor::Activate(void) |
|
{ |
|
BaseClass::Activate(); |
|
|
|
if (!m_hTargetEntity) |
|
{ |
|
m_hTargetEntity = gEntList.FindEntityByName( NULL, m_target ); |
|
} |
|
|
|
if (!m_hLookAtEntity && (m_nLookAtName != NULL_STRING)) |
|
{ |
|
m_hLookAtEntity = gEntList.FindEntityByName( NULL, m_nLookAtName ); |
|
if (!m_hLookAtEntity) |
|
{ |
|
DevMsg(1, "Angle sensor '%s' could not find look at entity '%s'.\n", GetDebugName(), STRING(m_nLookAtName)); |
|
} |
|
} |
|
|
|
// It's okay to not have a look at entity, it just means we measure and output the angles |
|
// of the target entity without testing them against the look at entity. |
|
if (!m_bDisabled && m_hTargetEntity) |
|
{ |
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Determines if one entity is facing within a given tolerance of another |
|
// Input : pEntity - |
|
// pTarget - |
|
// flTolerance - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CPointAngleSensor::IsFacingWithinTolerance(CBaseEntity *pEntity, CBaseEntity *pTarget, float flTolerance, float *pflDot) |
|
{ |
|
if (pflDot) |
|
{ |
|
*pflDot = 0; |
|
} |
|
|
|
if ((pEntity == NULL) || (pTarget == NULL)) |
|
{ |
|
return(false); |
|
} |
|
|
|
Vector forward; |
|
pEntity->GetVectors(&forward, NULL, NULL); |
|
|
|
Vector dir; |
|
// Use either our position relative to the target, or the target's raw facing |
|
if ( HasSpawnFlags( SF_USE_TARGET_FACING ) ) |
|
{ |
|
pTarget->GetVectors(&dir, NULL, NULL); |
|
} |
|
else |
|
{ |
|
dir = pTarget->GetAbsOrigin() - pEntity->GetAbsOrigin(); |
|
VectorNormalize(dir); |
|
} |
|
|
|
// |
|
// Larger dot product corresponds to a smaller angle. |
|
// |
|
float flDot = dir.Dot(forward); |
|
if (pflDot) |
|
{ |
|
*pflDot = flDot; |
|
} |
|
|
|
if (flDot >= m_flDotTolerance) |
|
{ |
|
return(true); |
|
} |
|
|
|
return(false); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called every frame. |
|
//----------------------------------------------------------------------------- |
|
void CPointAngleSensor::Think(void) |
|
{ |
|
if (m_hTargetEntity != NULL) |
|
{ |
|
Vector forward; |
|
m_hTargetEntity->GetVectors(&forward, NULL, NULL); |
|
m_TargetDir.Set(forward, this, this); |
|
|
|
if (m_hLookAtEntity != NULL) |
|
{ |
|
// |
|
// Check to see if the measure entity's forward vector has been within |
|
// given tolerance of the target entity for the given period of time. |
|
// |
|
float flDot; |
|
if (IsFacingWithinTolerance(m_hTargetEntity, m_hLookAtEntity, m_flDotTolerance, &flDot )) |
|
{ |
|
if (!m_bFired) |
|
{ |
|
if (!m_flFacingTime) |
|
{ |
|
m_flFacingTime = gpGlobals->curtime; |
|
} |
|
|
|
if (gpGlobals->curtime >= m_flFacingTime + m_flDuration) |
|
{ |
|
m_OnFacingLookat.FireOutput(this, this); |
|
m_bFired = true; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
// Reset the fired state |
|
if ( m_bFired ) |
|
{ |
|
m_bFired = false; |
|
} |
|
|
|
// Always reset the time when we've lost our facing |
|
m_flFacingTime = 0; |
|
} |
|
|
|
// Output the angle range we're in |
|
float flPerc = RemapValClamped( flDot, 1.0f, m_flDotTolerance, 1.0f, 0.0f ); |
|
m_FacingPercentage.Set( flPerc, this, this ); |
|
} |
|
|
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Input handler for forcing an instantaneous test of the condition. |
|
//----------------------------------------------------------------------------- |
|
void CPointAngleSensor::InputTest(inputdata_t &inputdata) |
|
{ |
|
if (IsFacingWithinTolerance(m_hTargetEntity, m_hLookAtEntity, m_flDotTolerance)) |
|
{ |
|
m_OnFacingLookat.FireOutput(inputdata.pActivator, this); |
|
} |
|
else |
|
{ |
|
m_OnNotFacingLookat.FireOutput(inputdata.pActivator, this); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPointAngleSensor::InputSetTargetEntity(inputdata_t &inputdata) |
|
{ |
|
if ((inputdata.value.String() == NULL) || (inputdata.value.StringID() == NULL_STRING) || (inputdata.value.String()[0] == '\0')) |
|
{ |
|
m_target = NULL_STRING; |
|
m_hTargetEntity = NULL; |
|
SetNextThink( TICK_NEVER_THINK ); |
|
} |
|
else |
|
{ |
|
m_target = AllocPooledString(inputdata.value.String()); |
|
m_hTargetEntity = gEntList.FindEntityByName( NULL, m_target, NULL, inputdata.pActivator, inputdata.pCaller ); |
|
if (!m_bDisabled && m_hTargetEntity) |
|
{ |
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPointAngleSensor::InputEnable(inputdata_t &inputdata) |
|
{ |
|
Enable(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPointAngleSensor::InputDisable(inputdata_t &inputdata) |
|
{ |
|
Disable(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: I like separators between my functions. |
|
//----------------------------------------------------------------------------- |
|
void CPointAngleSensor::InputToggle(inputdata_t &inputdata) |
|
{ |
|
if (m_bDisabled) |
|
{ |
|
Enable(); |
|
} |
|
else |
|
{ |
|
Disable(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPointAngleSensor::Enable() |
|
{ |
|
m_bDisabled = false; |
|
if (m_hTargetEntity) |
|
{ |
|
SetNextThink(gpGlobals->curtime); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPointAngleSensor::Disable() |
|
{ |
|
m_bDisabled = true; |
|
SetNextThink(TICK_NEVER_THINK); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CPointAngleSensor::DrawDebugTextOverlays(void) |
|
{ |
|
int nOffset = BaseClass::DrawDebugTextOverlays(); |
|
|
|
if (m_debugOverlays & OVERLAY_TEXT_BIT) |
|
{ |
|
float flDot; |
|
bool bFacing = IsFacingWithinTolerance(m_hTargetEntity, m_hLookAtEntity, m_flDotTolerance, &flDot); |
|
|
|
char tempstr[512]; |
|
Q_snprintf(tempstr, sizeof(tempstr), "delta ang (dot) : %.2f (%f)", RAD2DEG(acos(flDot)), flDot); |
|
EntityText( nOffset, tempstr, 0); |
|
nOffset++; |
|
|
|
Q_snprintf(tempstr, sizeof(tempstr), "tolerance ang (dot): %.2f (%f)", RAD2DEG(acos(m_flDotTolerance)), m_flDotTolerance); |
|
EntityText( nOffset, tempstr, 0); |
|
nOffset++; |
|
|
|
Q_snprintf(tempstr, sizeof(tempstr), "facing: %s", bFacing ? "yes" : "no"); |
|
EntityText( nOffset, tempstr, 0); |
|
nOffset++; |
|
} |
|
|
|
return nOffset; |
|
} |
|
|
|
// ==================================================================== |
|
// Proximity sensor |
|
// ==================================================================== |
|
|
|
#define SF_PROXIMITY_TEST_AGAINST_AXIS (1<<0) |
|
|
|
class CPointProximitySensor : public CPointEntity |
|
{ |
|
DECLARE_CLASS( CPointProximitySensor, CPointEntity ); |
|
|
|
public: |
|
|
|
virtual void Activate( void ); |
|
|
|
protected: |
|
|
|
void Think( void ); |
|
void Enable( void ); |
|
void Disable( void ); |
|
|
|
// Input handlers |
|
void InputEnable(inputdata_t &inputdata); |
|
void InputDisable(inputdata_t &inputdata); |
|
void InputToggle(inputdata_t &inputdata); |
|
void InputSetTargetEntity(inputdata_t &inputdata); |
|
|
|
private: |
|
|
|
bool m_bDisabled; // When disabled, we do not think or fire outputs. |
|
EHANDLE m_hTargetEntity; // Entity whose angles are being monitored. |
|
|
|
COutputFloat m_Distance; |
|
|
|
DECLARE_DATADESC(); |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( point_proximity_sensor, CPointProximitySensor ); |
|
|
|
BEGIN_DATADESC( CPointProximitySensor ) |
|
|
|
// Keys |
|
DEFINE_KEYFIELD( m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), |
|
DEFINE_FIELD( m_hTargetEntity, FIELD_EHANDLE ), |
|
|
|
// Outputs |
|
DEFINE_OUTPUT( m_Distance, "Distance"), |
|
|
|
// Inputs |
|
DEFINE_INPUTFUNC(FIELD_VOID, "Enable", InputEnable), |
|
DEFINE_INPUTFUNC(FIELD_VOID, "Disable", InputDisable), |
|
DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle), |
|
DEFINE_INPUTFUNC(FIELD_STRING, "SetTargetEntity", InputSetTargetEntity), |
|
|
|
END_DATADESC() |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called after all entities have spawned on new map or savegame load. |
|
//----------------------------------------------------------------------------- |
|
void CPointProximitySensor::Activate( void ) |
|
{ |
|
BaseClass::Activate(); |
|
|
|
if ( m_hTargetEntity == NULL ) |
|
{ |
|
m_hTargetEntity = gEntList.FindEntityByName( NULL, m_target ); |
|
} |
|
|
|
if ( m_bDisabled == false && m_hTargetEntity != NULL ) |
|
{ |
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPointProximitySensor::InputSetTargetEntity(inputdata_t &inputdata) |
|
{ |
|
if ((inputdata.value.String() == NULL) || (inputdata.value.StringID() == NULL_STRING) || (inputdata.value.String()[0] == '\0')) |
|
{ |
|
m_target = NULL_STRING; |
|
m_hTargetEntity = NULL; |
|
SetNextThink( TICK_NEVER_THINK ); |
|
} |
|
else |
|
{ |
|
m_target = AllocPooledString(inputdata.value.String()); |
|
m_hTargetEntity = gEntList.FindEntityByName( NULL, m_target, NULL, inputdata.pActivator, inputdata.pCaller ); |
|
if (!m_bDisabled && m_hTargetEntity) |
|
{ |
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPointProximitySensor::InputEnable( inputdata_t &inputdata ) |
|
{ |
|
Enable(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPointProximitySensor::InputDisable( inputdata_t &inputdata ) |
|
{ |
|
Disable(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPointProximitySensor::InputToggle( inputdata_t &inputdata ) |
|
{ |
|
if ( m_bDisabled ) |
|
{ |
|
Enable(); |
|
} |
|
else |
|
{ |
|
Disable(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPointProximitySensor::Enable( void ) |
|
{ |
|
m_bDisabled = false; |
|
if ( m_hTargetEntity ) |
|
{ |
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CPointProximitySensor::Disable( void ) |
|
{ |
|
m_bDisabled = true; |
|
SetNextThink( TICK_NEVER_THINK ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Called every frame |
|
//----------------------------------------------------------------------------- |
|
void CPointProximitySensor::Think( void ) |
|
{ |
|
if ( m_hTargetEntity != NULL ) |
|
{ |
|
Vector vecTestDir = ( m_hTargetEntity->GetAbsOrigin() - GetAbsOrigin() ); |
|
float flDist = VectorNormalize( vecTestDir ); |
|
|
|
// If we're only interested in the distance along a vector, modify the length the accomodate that |
|
if ( HasSpawnFlags( SF_PROXIMITY_TEST_AGAINST_AXIS ) ) |
|
{ |
|
Vector vecDir; |
|
GetVectors( &vecDir, NULL, NULL ); |
|
|
|
float flDot = DotProduct( vecTestDir, vecDir ); |
|
flDist *= fabs( flDot ); |
|
} |
|
|
|
m_Distance.Set( flDist, this, this ); |
|
SetNextThink( gpGlobals->curtime ); |
|
} |
|
}
|
|
|