From 39ed87570bdb2f86969d4be821c94b722dc71179 Mon Sep 17 00:00:00 2001 From: Joe Ludwig Date: Wed, 26 Jun 2013 15:22:04 -0700 Subject: First version of the SOurce SDK 2013 --- mp/src/game/server/logicentities.cpp | 2748 ++++++++++++++++++++++++++++++++++ 1 file changed, 2748 insertions(+) create mode 100644 mp/src/game/server/logicentities.cpp (limited to 'mp/src/game/server/logicentities.cpp') diff --git a/mp/src/game/server/logicentities.cpp b/mp/src/game/server/logicentities.cpp new file mode 100644 index 00000000..a53298b0 --- /dev/null +++ b/mp/src/game/server/logicentities.cpp @@ -0,0 +1,2748 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Implements many of the entities that control logic flow within a map. +// +//============================================================================= + +#include "cbase.h" +#include "entityinput.h" +#include "entityoutput.h" +#include "eventqueue.h" +#include "mathlib/mathlib.h" +#include "globalstate.h" +#include "ndebugoverlay.h" +#include "saverestore_utlvector.h" +#include "vstdlib/random.h" +#include "gameinterface.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +extern CServerGameDLL g_ServerGameDLL; + + +//----------------------------------------------------------------------------- +// Purpose: Compares a set of integer inputs to the one main input +// Outputs true if they are all equivalant, false otherwise +//----------------------------------------------------------------------------- +class CLogicCompareInteger : public CLogicalEntity +{ +public: + DECLARE_CLASS( CLogicCompareInteger, CLogicalEntity ); + + // outputs + COutputEvent m_OnEqual; + COutputEvent m_OnNotEqual; + + // data + int m_iIntegerValue; + int m_iShouldCompareToValue; + + DECLARE_DATADESC(); + + CMultiInputVar m_AllIntCompares; + + // Input handlers + void InputValue( inputdata_t &inputdata ); + void InputCompareValues( inputdata_t &inputdata ); +}; + + +LINK_ENTITY_TO_CLASS( logic_multicompare, CLogicCompareInteger ); + + +BEGIN_DATADESC( CLogicCompareInteger ) + + DEFINE_OUTPUT( m_OnEqual, "OnEqual" ), + DEFINE_OUTPUT( m_OnNotEqual, "OnNotEqual" ), + + DEFINE_KEYFIELD( m_iIntegerValue, FIELD_INTEGER, "IntegerValue" ), + DEFINE_KEYFIELD( m_iShouldCompareToValue, FIELD_INTEGER, "ShouldComparetoValue" ), + + DEFINE_FIELD( m_AllIntCompares, FIELD_INPUT ), + + DEFINE_INPUTFUNC( FIELD_INPUT, "InputValue", InputValue ), + DEFINE_INPUTFUNC( FIELD_INPUT, "CompareValues", InputCompareValues ), + +END_DATADESC() + + + + +//----------------------------------------------------------------------------- +// Purpose: Adds to the list of compared values +//----------------------------------------------------------------------------- +void CLogicCompareInteger::InputValue( inputdata_t &inputdata ) +{ + // make sure it's an int, if it can't be converted just throw it away + if ( !inputdata.value.Convert(FIELD_INTEGER) ) + return; + + // update the value list with the new value + m_AllIntCompares.AddValue( inputdata.value, inputdata.nOutputID ); + + // if we haven't already this frame, send a message to ourself to update and fire + if ( !m_AllIntCompares.m_bUpdatedThisFrame ) + { + // TODO: need to add this event with a lower priority, so it gets called after all inputs have arrived + g_EventQueue.AddEvent( this, "CompareValues", 0, inputdata.pActivator, this, inputdata.nOutputID ); + m_AllIntCompares.m_bUpdatedThisFrame = TRUE; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Forces a recompare +//----------------------------------------------------------------------------- +void CLogicCompareInteger::InputCompareValues( inputdata_t &inputdata ) +{ + m_AllIntCompares.m_bUpdatedThisFrame = FALSE; + + // loop through all the values comparing them + int value = m_iIntegerValue; + CMultiInputVar::inputitem_t *input = m_AllIntCompares.m_InputList; + + if ( !m_iShouldCompareToValue && input ) + { + value = input->value.Int(); + } + + while ( input ) + { + if ( input->value.Int() != value ) + { + // false + m_OnNotEqual.FireOutput( inputdata.pActivator, this ); + return; + } + + input = input->next; + } + + // true! all values equal + m_OnEqual.FireOutput( inputdata.pActivator, this ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Timer entity. Fires an output at regular or random intervals. +//----------------------------------------------------------------------------- +// +// Spawnflags and others constants. +// +const int SF_TIMER_UPDOWN = 1; +const float LOGIC_TIMER_MIN_INTERVAL = 0.01; + + +class CTimerEntity : public CLogicalEntity +{ +public: + DECLARE_CLASS( CTimerEntity, CLogicalEntity ); + + void Spawn( void ); + void Think( void ); + + void Toggle( void ); + void Enable( void ); + void Disable( void ); + void FireTimer( void ); + + int DrawDebugTextOverlays(void); + + // outputs + COutputEvent m_OnTimer; + COutputEvent m_OnTimerHigh; + COutputEvent m_OnTimerLow; + + // inputs + void InputToggle( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + void InputFireTimer( inputdata_t &inputdata ); + void InputRefireTime( inputdata_t &inputdata ); + void InputResetTimer( inputdata_t &inputdata ); + void InputAddToTimer( inputdata_t &inputdata ); + void InputSubtractFromTimer( inputdata_t &inputdata ); + + int m_iDisabled; + float m_flRefireTime; + bool m_bUpDownState; + int m_iUseRandomTime; + float m_flLowerRandomBound; + float m_flUpperRandomBound; + + // methods + void ResetTimer( void ); + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS( logic_timer, CTimerEntity ); + + +BEGIN_DATADESC( CTimerEntity ) + + // Keys + DEFINE_KEYFIELD( m_iDisabled, FIELD_INTEGER, "StartDisabled" ), + DEFINE_KEYFIELD( m_flRefireTime, FIELD_FLOAT, "RefireTime" ), + + DEFINE_FIELD( m_bUpDownState, FIELD_BOOLEAN ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_FLOAT, "RefireTime", InputRefireTime ), + DEFINE_INPUTFUNC( FIELD_VOID, "FireTimer", InputFireTimer ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "AddToTimer", InputAddToTimer ), + DEFINE_INPUTFUNC( FIELD_VOID, "ResetTimer", InputResetTimer ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SubtractFromTimer", InputSubtractFromTimer ), + + DEFINE_INPUT( m_iUseRandomTime, FIELD_INTEGER, "UseRandomTime" ), + DEFINE_INPUT( m_flLowerRandomBound, FIELD_FLOAT, "LowerRandomBound" ), + DEFINE_INPUT( m_flUpperRandomBound, FIELD_FLOAT, "UpperRandomBound" ), + + + // Outputs + DEFINE_OUTPUT( m_OnTimer, "OnTimer" ), + DEFINE_OUTPUT( m_OnTimerHigh, "OnTimerHigh" ), + DEFINE_OUTPUT( m_OnTimerLow, "OnTimerLow" ), + +END_DATADESC() + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTimerEntity::Spawn( void ) +{ + if (!m_iUseRandomTime && (m_flRefireTime < LOGIC_TIMER_MIN_INTERVAL)) + { + m_flRefireTime = LOGIC_TIMER_MIN_INTERVAL; + } + + if ( !m_iDisabled && (m_flRefireTime > 0 || m_iUseRandomTime) ) + { + Enable(); + } + else + { + Disable(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTimerEntity::Think( void ) +{ + FireTimer(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Sets the time the timerentity will next fire +//----------------------------------------------------------------------------- +void CTimerEntity::ResetTimer( void ) +{ + if ( m_iDisabled ) + return; + + if ( m_iUseRandomTime ) + { + m_flRefireTime = random->RandomFloat( m_flLowerRandomBound, m_flUpperRandomBound ); + } + + SetNextThink( gpGlobals->curtime + m_flRefireTime ); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTimerEntity::Enable( void ) +{ + m_iDisabled = FALSE; + ResetTimer(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTimerEntity::Disable( void ) +{ + m_iDisabled = TRUE; + SetNextThink( TICK_NEVER_THINK ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTimerEntity::Toggle( void ) +{ + if ( m_iDisabled ) + { + Enable(); + } + else + { + Disable(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTimerEntity::FireTimer( void ) +{ + if ( !m_iDisabled ) + { + // + // Up/down timers alternate between two outputs. + // + if (m_spawnflags & SF_TIMER_UPDOWN) + { + if (m_bUpDownState) + { + m_OnTimerHigh.FireOutput( this, this ); + } + else + { + m_OnTimerLow.FireOutput( this, this ); + } + + m_bUpDownState = !m_bUpDownState; + } + // + // Regular timers only fire a single output. + // + else + { + m_OnTimer.FireOutput( this, this ); + } + + ResetTimer(); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTimerEntity::InputEnable( inputdata_t &inputdata ) +{ + Enable(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTimerEntity::InputDisable( inputdata_t &inputdata ) +{ + Disable(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTimerEntity::InputToggle( inputdata_t &inputdata ) +{ + Toggle(); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CTimerEntity::InputFireTimer( inputdata_t &inputdata ) +{ + FireTimer(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Changes the time interval between timer fires +// Resets the next firing to be time + newRefireTime +// Input : Float refire frequency in seconds. +//----------------------------------------------------------------------------- +void CTimerEntity::InputRefireTime( inputdata_t &inputdata ) +{ + float flRefireInterval = inputdata.value.Float(); + + if ( flRefireInterval < LOGIC_TIMER_MIN_INTERVAL) + { + flRefireInterval = LOGIC_TIMER_MIN_INTERVAL; + } + + if (m_flRefireTime != flRefireInterval ) + { + m_flRefireTime = flRefireInterval; + ResetTimer(); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CTimerEntity::InputResetTimer( inputdata_t &inputdata ) +{ + // don't reset the timer if it isn't enabled + if ( m_iDisabled ) + return; + + ResetTimer(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Adds to the time interval if the timer is enabled +// Input : Float time to add in seconds +//----------------------------------------------------------------------------- +void CTimerEntity::InputAddToTimer( inputdata_t &inputdata ) +{ + // don't add time if the timer isn't enabled + if ( m_iDisabled ) + return; + + // Add time to timer + float flNextThink = GetNextThink(); + SetNextThink( flNextThink += inputdata.value.Float() ); +} + +//----------------------------------------------------------------------------- +// Purpose: Subtract from the time interval if the timer is enabled +// Input : Float time to subtract in seconds +//----------------------------------------------------------------------------- +void CTimerEntity::InputSubtractFromTimer( inputdata_t &inputdata ) +{ + // don't add time if the timer isn't enabled + if ( m_iDisabled ) + return; + + // Subtract time from the timer but don't let the timer go negative + float flNextThink = GetNextThink(); + if ( ( flNextThink - gpGlobals->curtime ) <= inputdata.value.Float() ) + { + SetNextThink( gpGlobals->curtime ); + } + else + { + SetNextThink( flNextThink -= inputdata.value.Float() ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CTimerEntity::DrawDebugTextOverlays( void ) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + + // print refire time + Q_snprintf(tempstr,sizeof(tempstr),"refire interval: %.2f sec", m_flRefireTime); + EntityText(text_offset,tempstr,0); + text_offset++; + + // print seconds to next fire + if ( !m_iDisabled ) + { + float flNextThink = GetNextThink(); + Q_snprintf( tempstr, sizeof( tempstr ), " firing in: %.2f sec", flNextThink - gpGlobals->curtime ); + EntityText( text_offset, tempstr, 0); + text_offset++; + } + } + return text_offset; +} + + +//----------------------------------------------------------------------------- +// Purpose: Computes a line between two entities +//----------------------------------------------------------------------------- +class CLogicLineToEntity : public CLogicalEntity +{ +public: + DECLARE_CLASS( CLogicLineToEntity, CLogicalEntity ); + + void Activate(void); + void Spawn( void ); + void Think( void ); + + // outputs + COutputVector m_Line; + + DECLARE_DATADESC(); + +private: + string_t m_SourceName; + EHANDLE m_StartEntity; + EHANDLE m_EndEntity; +}; + +LINK_ENTITY_TO_CLASS( logic_lineto, CLogicLineToEntity ); + + +BEGIN_DATADESC( CLogicLineToEntity ) + + // Keys + // target is handled in the base class, stored in field m_target + DEFINE_KEYFIELD( m_SourceName, FIELD_STRING, "source" ), + DEFINE_FIELD( m_StartEntity, FIELD_EHANDLE ), + DEFINE_FIELD( m_EndEntity, FIELD_EHANDLE ), + + // Outputs + DEFINE_OUTPUT( m_Line, "Line" ), + +END_DATADESC() + + + +//----------------------------------------------------------------------------- +// Find the entities +//----------------------------------------------------------------------------- +void CLogicLineToEntity::Activate(void) +{ + BaseClass::Activate(); + + if (m_target != NULL_STRING) + { + m_EndEntity = gEntList.FindEntityByName( NULL, m_target ); + + // + // If we were given a bad measure target, just measure sound where we are. + // + if ((m_EndEntity == NULL) || (m_EndEntity->edict() == NULL)) + { + Warning( "logic_lineto - Target not found or target with no origin!\n"); + m_EndEntity = this; + } + } + else + { + m_EndEntity = this; + } + + if (m_SourceName != NULL_STRING) + { + m_StartEntity = gEntList.FindEntityByName( NULL, m_SourceName ); + + // + // If we were given a bad measure target, just measure sound where we are. + // + if ((m_StartEntity == NULL) || (m_StartEntity->edict() == NULL)) + { + Warning( "logic_lineto - Source not found or source with no origin!\n"); + m_StartEntity = this; + } + } + else + { + m_StartEntity = this; + } +} + + +//----------------------------------------------------------------------------- +// Find the entities +//----------------------------------------------------------------------------- +void CLogicLineToEntity::Spawn(void) +{ + SetNextThink( gpGlobals->curtime + 0.01f ); +} + + +//----------------------------------------------------------------------------- +// Find the entities +//----------------------------------------------------------------------------- +void CLogicLineToEntity::Think(void) +{ + CBaseEntity* pDest = m_EndEntity.Get(); + CBaseEntity* pSrc = m_StartEntity.Get(); + if (!pDest || !pSrc || !pDest->edict() || !pSrc->edict()) + { + // Can sleep for a long time, no more lines. + m_Line.Set( vec3_origin, this, this ); + SetNextThink( gpGlobals->curtime + 10 ); + return; + } + + Vector delta; + VectorSubtract( pDest->GetAbsOrigin(), pSrc->GetAbsOrigin(), delta ); + m_Line.Set(delta, this, this); + + SetNextThink( gpGlobals->curtime + 0.01f ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Remaps a given input range to an output range. +//----------------------------------------------------------------------------- +const int SF_MATH_REMAP_IGNORE_OUT_OF_RANGE = 1; +const int SF_MATH_REMAP_CLAMP_OUTPUT_TO_RANGE = 2; + +class CMathRemap : public CLogicalEntity +{ +public: + + DECLARE_CLASS( CMathRemap, CLogicalEntity ); + + void Spawn(void); + + // Keys + float m_flInMin; + float m_flInMax; + float m_flOut1; // Output value when input is m_fInMin + float m_flOut2; // Output value when input is m_fInMax + + bool m_bEnabled; + + // Inputs + void InputValue( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + + // Outputs + COutputFloat m_OutValue; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(math_remap, CMathRemap); + + +BEGIN_DATADESC( CMathRemap ) + + DEFINE_INPUTFUNC(FIELD_FLOAT, "InValue", InputValue ), + + DEFINE_OUTPUT(m_OutValue, "OutValue"), + + DEFINE_KEYFIELD(m_flInMin, FIELD_FLOAT, "in1"), + DEFINE_KEYFIELD(m_flInMax, FIELD_FLOAT, "in2"), + DEFINE_KEYFIELD(m_flOut1, FIELD_FLOAT, "out1"), + DEFINE_KEYFIELD(m_flOut2, FIELD_FLOAT, "out2"), + + DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), + + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + +END_DATADESC() + + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathRemap::Spawn(void) +{ + // + // Avoid a divide by zero in ValueChanged. + // + if (m_flInMin == m_flInMax) + { + m_flInMin = 0; + m_flInMax = 1; + } + + // + // Make sure min and max are set properly relative to one another. + // + if (m_flInMin > m_flInMax) + { + float flTemp = m_flInMin; + m_flInMin = m_flInMax; + m_flInMax = flTemp; + } + + m_bEnabled = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathRemap::InputEnable( inputdata_t &inputdata ) +{ + m_bEnabled = true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathRemap::InputDisable( inputdata_t &inputdata ) +{ + m_bEnabled = false; +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler that is called when the input value changes. +//----------------------------------------------------------------------------- +void CMathRemap::InputValue( inputdata_t &inputdata ) +{ + float flValue = inputdata.value.Float(); + + // + // Disallow out-of-range input values to avoid out-of-range output values. + // + float flClampValue = clamp(flValue, m_flInMin, m_flInMax); + + if ((flClampValue == flValue) || !FBitSet(m_spawnflags, SF_MATH_REMAP_IGNORE_OUT_OF_RANGE)) + { + // + // Remap the input value to the desired output range and update the output. + // + float flRemappedValue = m_flOut1 + (((flValue - m_flInMin) * (m_flOut2 - m_flOut1)) / (m_flInMax - m_flInMin)); + + if ( FBitSet( m_spawnflags, SF_MATH_REMAP_CLAMP_OUTPUT_TO_RANGE ) ) + { + flRemappedValue = clamp( flRemappedValue, m_flOut1, m_flOut2 ); + } + + if ( m_bEnabled == true ) + { + m_OutValue.Set(flRemappedValue, inputdata.pActivator, this); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Remaps a given input range to an output range. +//----------------------------------------------------------------------------- +const int SF_COLOR_BLEND_IGNORE_OUT_OF_RANGE = 1; + +class CMathColorBlend : public CLogicalEntity +{ +public: + + DECLARE_CLASS( CMathColorBlend, CLogicalEntity ); + + void Spawn(void); + + // Keys + float m_flInMin; + float m_flInMax; + color32 m_OutColor1; // Output color when input is m_fInMin + color32 m_OutColor2; // Output color when input is m_fInMax + + // Inputs + void InputValue( inputdata_t &inputdata ); + + // Outputs + COutputColor32 m_OutValue; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(math_colorblend, CMathColorBlend); + + +BEGIN_DATADESC( CMathColorBlend ) + + DEFINE_INPUTFUNC(FIELD_FLOAT, "InValue", InputValue ), + + DEFINE_OUTPUT(m_OutValue, "OutColor"), + + DEFINE_KEYFIELD(m_flInMin, FIELD_FLOAT, "inmin"), + DEFINE_KEYFIELD(m_flInMax, FIELD_FLOAT, "inmax"), + DEFINE_KEYFIELD(m_OutColor1, FIELD_COLOR32, "colormin"), + DEFINE_KEYFIELD(m_OutColor2, FIELD_COLOR32, "colormax"), + +END_DATADESC() + + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMathColorBlend::Spawn(void) +{ + // + // Avoid a divide by zero in ValueChanged. + // + if (m_flInMin == m_flInMax) + { + m_flInMin = 0; + m_flInMax = 1; + } + + // + // Make sure min and max are set properly relative to one another. + // + if (m_flInMin > m_flInMax) + { + float flTemp = m_flInMin; + m_flInMin = m_flInMax; + m_flInMax = flTemp; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler that is called when the input value changes. +//----------------------------------------------------------------------------- +void CMathColorBlend::InputValue( inputdata_t &inputdata ) +{ + float flValue = inputdata.value.Float(); + + // + // Disallow out-of-range input values to avoid out-of-range output values. + // + float flClampValue = clamp(flValue, m_flInMin, m_flInMax); + if ((flClampValue == flValue) || !FBitSet(m_spawnflags, SF_COLOR_BLEND_IGNORE_OUT_OF_RANGE)) + { + // + // Remap the input value to the desired output color and update the output. + // + color32 Color; + Color.r = m_OutColor1.r + (((flClampValue - m_flInMin) * (m_OutColor2.r - m_OutColor1.r)) / (m_flInMax - m_flInMin)); + Color.g = m_OutColor1.g + (((flClampValue - m_flInMin) * (m_OutColor2.g - m_OutColor1.g)) / (m_flInMax - m_flInMin)); + Color.b = m_OutColor1.b + (((flClampValue - m_flInMin) * (m_OutColor2.b - m_OutColor1.b)) / (m_flInMax - m_flInMin)); + Color.a = m_OutColor1.a + (((flClampValue - m_flInMin) * (m_OutColor2.a - m_OutColor1.a)) / (m_flInMax - m_flInMin)); + + m_OutValue.Set(Color, inputdata.pActivator, this); + } +} + + +//----------------------------------------------------------------------------- +// Console command to set the state of a global +//----------------------------------------------------------------------------- +void CC_Global_Set( const CCommand &args ) +{ + const char *szGlobal = args[1]; + const char *szState = args[2]; + + if ( szGlobal == NULL || szState == NULL ) + { + Msg( "Usage: global_set : Sets the state of the given env_global (0 = OFF, 1 = ON, 2 = DEAD).\n" ); + return; + } + + int nState = atoi( szState ); + + int nIndex = GlobalEntity_GetIndex( szGlobal ); + + if ( nIndex >= 0 ) + { + GlobalEntity_SetState( nIndex, ( GLOBALESTATE )nState ); + } + else + { + GlobalEntity_Add( szGlobal, STRING( gpGlobals->mapname ), ( GLOBALESTATE )nState ); + } +} + +static ConCommand global_set( "global_set", CC_Global_Set, "global_set : Sets the state of the given env_global (0 = OFF, 1 = ON, 2 = DEAD).", FCVAR_CHEAT ); + + +//----------------------------------------------------------------------------- +// Purpose: Holds a global state that can be queried by other entities to change +// their behavior, such as "predistaster". +//----------------------------------------------------------------------------- +const int SF_GLOBAL_SET = 1; // Set global state to initial state on spawn + +class CEnvGlobal : public CLogicalEntity +{ +public: + DECLARE_CLASS( CEnvGlobal, CLogicalEntity ); + + void Spawn( void ); + + // Input handlers + void InputTurnOn( inputdata_t &inputdata ); + void InputTurnOff( inputdata_t &inputdata ); + void InputRemove( inputdata_t &inputdata ); + void InputToggle( inputdata_t &inputdata ); + void InputSetCounter( inputdata_t &inputdata ); + void InputAddToCounter( inputdata_t &inputdata ); + void InputGetCounter( inputdata_t &inputdata ); + + int DrawDebugTextOverlays(void); + + DECLARE_DATADESC(); + + COutputInt m_outCounter; + + string_t m_globalstate; + int m_triggermode; + int m_initialstate; + int m_counter; // A counter value associated with this global. +}; + + +BEGIN_DATADESC( CEnvGlobal ) + + DEFINE_KEYFIELD( m_globalstate, FIELD_STRING, "globalstate" ), + DEFINE_FIELD( m_triggermode, FIELD_INTEGER ), + DEFINE_KEYFIELD( m_initialstate, FIELD_INTEGER, "initialstate" ), + DEFINE_KEYFIELD( m_counter, FIELD_INTEGER, "counter" ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "TurnOn", InputTurnOn ), + DEFINE_INPUTFUNC( FIELD_VOID, "TurnOff", InputTurnOff ), + DEFINE_INPUTFUNC( FIELD_VOID, "Remove", InputRemove ), + DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), + + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetCounter", InputSetCounter ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "AddToCounter", InputAddToCounter ), + DEFINE_INPUTFUNC( FIELD_VOID, "GetCounter", InputGetCounter ), + + DEFINE_OUTPUT( m_outCounter, "Counter" ), + +END_DATADESC() + + +LINK_ENTITY_TO_CLASS( env_global, CEnvGlobal ); + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CEnvGlobal::Spawn( void ) +{ + if ( !m_globalstate ) + { + UTIL_Remove( this ); + return; + } + +#ifdef HL2_EPISODIC + // if we modify the state of the physics cannon, make sure we precache the ragdoll boogie zap sound + if ( ( m_globalstate != NULL_STRING ) && ( stricmp( STRING( m_globalstate ), "super_phys_gun" ) == 0 ) ) + { + PrecacheScriptSound( "RagdollBoogie.Zap" ); + } +#endif + + if ( FBitSet( m_spawnflags, SF_GLOBAL_SET ) ) + { + if ( !GlobalEntity_IsInTable( m_globalstate ) ) + { + GlobalEntity_Add( m_globalstate, gpGlobals->mapname, (GLOBALESTATE)m_initialstate ); + } + + if ( m_counter != 0 ) + { + GlobalEntity_SetCounter( m_globalstate, m_counter ); + } + } +} + + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CEnvGlobal::InputTurnOn( inputdata_t &inputdata ) +{ + if ( GlobalEntity_IsInTable( m_globalstate ) ) + { + GlobalEntity_SetState( m_globalstate, GLOBAL_ON ); + } + else + { + GlobalEntity_Add( m_globalstate, gpGlobals->mapname, GLOBAL_ON ); + } +} + + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CEnvGlobal::InputTurnOff( inputdata_t &inputdata ) +{ + if ( GlobalEntity_IsInTable( m_globalstate ) ) + { + GlobalEntity_SetState( m_globalstate, GLOBAL_OFF ); + } + else + { + GlobalEntity_Add( m_globalstate, gpGlobals->mapname, GLOBAL_OFF ); + } +} + + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CEnvGlobal::InputRemove( inputdata_t &inputdata ) +{ + if ( GlobalEntity_IsInTable( m_globalstate ) ) + { + GlobalEntity_SetState( m_globalstate, GLOBAL_DEAD ); + } + else + { + GlobalEntity_Add( m_globalstate, gpGlobals->mapname, GLOBAL_DEAD ); + } +} + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void CEnvGlobal::InputSetCounter( inputdata_t &inputdata ) +{ + if ( !GlobalEntity_IsInTable( m_globalstate ) ) + { + GlobalEntity_Add( m_globalstate, gpGlobals->mapname, GLOBAL_ON ); + } + + GlobalEntity_SetCounter( m_globalstate, inputdata.value.Int() ); +} + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void CEnvGlobal::InputAddToCounter( inputdata_t &inputdata ) +{ + if ( !GlobalEntity_IsInTable( m_globalstate ) ) + { + GlobalEntity_Add( m_globalstate, gpGlobals->mapname, GLOBAL_ON ); + } + + GlobalEntity_AddToCounter( m_globalstate, inputdata.value.Int() ); +} + + +//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ +void CEnvGlobal::InputGetCounter( inputdata_t &inputdata ) +{ + if ( !GlobalEntity_IsInTable( m_globalstate ) ) + { + GlobalEntity_Add( m_globalstate, gpGlobals->mapname, GLOBAL_ON ); + } + + m_outCounter.Set( GlobalEntity_GetCounter( m_globalstate ), inputdata.pActivator, this ); +} + + +//------------------------------------------------------------------------------ +// Purpose: +//------------------------------------------------------------------------------ +void CEnvGlobal::InputToggle( inputdata_t &inputdata ) +{ + GLOBALESTATE oldState = GlobalEntity_GetState( m_globalstate ); + GLOBALESTATE newState; + + if ( oldState == GLOBAL_ON ) + { + newState = GLOBAL_OFF; + } + else if ( oldState == GLOBAL_OFF ) + { + newState = GLOBAL_ON; + } + else + { + return; + } + + if ( GlobalEntity_IsInTable( m_globalstate ) ) + { + GlobalEntity_SetState( m_globalstate, newState ); + } + else + { + GlobalEntity_Add( m_globalstate, gpGlobals->mapname, newState ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Input : +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CEnvGlobal::DrawDebugTextOverlays(void) +{ + // Skip AIClass debug overlays + int text_offset = CBaseEntity::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + Q_snprintf(tempstr,sizeof(tempstr),"State: %s",STRING(m_globalstate)); + EntityText(text_offset,tempstr,0); + text_offset++; + + GLOBALESTATE nState = GlobalEntity_GetState( m_globalstate ); + + switch( nState ) + { + case GLOBAL_OFF: + Q_strncpy(tempstr,"Value: OFF",sizeof(tempstr)); + break; + + case GLOBAL_ON: + Q_strncpy(tempstr,"Value: ON",sizeof(tempstr)); + break; + + case GLOBAL_DEAD: + Q_strncpy(tempstr,"Value: DEAD",sizeof(tempstr)); + break; + } + EntityText(text_offset,tempstr,0); + text_offset++; + } + return text_offset; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +#define MS_MAX_TARGETS 32 + +const int SF_MULTI_INIT = 1; + +class CMultiSource : public CLogicalEntity +{ +public: + DECLARE_CLASS( CMultiSource, CLogicalEntity ); + + void Spawn( ); + bool KeyValue( const char *szKeyName, const char *szValue ); + void Use( ::CBaseEntity *pActivator, ::CBaseEntity *pCaller, USE_TYPE useType, float value ); + int ObjectCaps( void ) { return(BaseClass::ObjectCaps() | FCAP_MASTER); } + bool IsTriggered( ::CBaseEntity *pActivator ); + void Register( void ); + + DECLARE_DATADESC(); + + EHANDLE m_rgEntities[MS_MAX_TARGETS]; + int m_rgTriggered[MS_MAX_TARGETS]; + + COutputEvent m_OnTrigger; // Fired when all connections are triggered. + + int m_iTotal; + string_t m_globalstate; +}; + +BEGIN_DATADESC( CMultiSource ) + + //!!!BUGBUG FIX + DEFINE_ARRAY( m_rgEntities, FIELD_EHANDLE, MS_MAX_TARGETS ), + DEFINE_ARRAY( m_rgTriggered, FIELD_INTEGER, MS_MAX_TARGETS ), + DEFINE_FIELD( m_iTotal, FIELD_INTEGER ), + + DEFINE_KEYFIELD( m_globalstate, FIELD_STRING, "globalstate" ), + + // Function pointers + DEFINE_FUNCTION( Register ), + + // Outputs + DEFINE_OUTPUT(m_OnTrigger, "OnTrigger"), + +END_DATADESC() + + +LINK_ENTITY_TO_CLASS( multisource, CMultiSource ); + + +//----------------------------------------------------------------------------- +// Purpose: Cache user entity field values until spawn is called. +// Input : szKeyName - Key to handle. +// szValue - Value for key. +// Output : Returns true if the key was handled, false if not. +//----------------------------------------------------------------------------- +bool CMultiSource::KeyValue( const char *szKeyName, const char *szValue ) +{ + if ( FStrEq(szKeyName, "style") || + FStrEq(szKeyName, "height") || + FStrEq(szKeyName, "killtarget") || + FStrEq(szKeyName, "value1") || + FStrEq(szKeyName, "value2") || + FStrEq(szKeyName, "value3")) + { + } + else + { + return BaseClass::KeyValue( szKeyName, szValue ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMultiSource::Spawn() +{ + SetNextThink( gpGlobals->curtime + 0.1f ); + m_spawnflags |= SF_MULTI_INIT; // Until it's initialized + SetThink(&CMultiSource::Register); +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : pActivator - +// pCaller - +// useType - +// value - +//----------------------------------------------------------------------------- +void CMultiSource::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + int i = 0; + + // Find the entity in our list + while (i < m_iTotal) + if ( m_rgEntities[i++] == pCaller ) + break; + + // if we didn't find it, report error and leave + if (i > m_iTotal) + { + Warning("MultiSrc: Used by non member %s.\n", pCaller->edict() ? pCaller->GetClassname() : ""); + return; + } + + // CONSIDER: a Use input to the multisource always toggles. Could check useType for ON/OFF/TOGGLE + + m_rgTriggered[i-1] ^= 1; + + // + if ( IsTriggered( pActivator ) ) + { + DevMsg( 2, "Multisource %s enabled (%d inputs)\n", GetDebugName(), m_iTotal ); + USE_TYPE useType = USE_TOGGLE; + if ( m_globalstate != NULL_STRING ) + useType = USE_ON; + + m_OnTrigger.FireOutput(pActivator, this); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CMultiSource::IsTriggered( CBaseEntity * ) +{ + // Is everything triggered? + int i = 0; + + // Still initializing? + if ( m_spawnflags & SF_MULTI_INIT ) + return 0; + + while (i < m_iTotal) + { + if (m_rgTriggered[i] == 0) + break; + i++; + } + + if (i == m_iTotal) + { + if ( !m_globalstate || GlobalEntity_GetState( m_globalstate ) == GLOBAL_ON ) + return 1; + } + + return 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CMultiSource::Register(void) +{ + CBaseEntity *pTarget = NULL; + + m_iTotal = 0; + memset( m_rgEntities, 0, MS_MAX_TARGETS * sizeof(EHANDLE) ); + + SetThink(&CMultiSource::SUB_DoNothing); + + // search for all entities which target this multisource (m_iName) + // dvsents2: port multisource to entity I/O! + + pTarget = gEntList.FindEntityByTarget( NULL, STRING(GetEntityName()) ); + + while ( pTarget && (m_iTotal < MS_MAX_TARGETS) ) + { + if ( pTarget ) + m_rgEntities[m_iTotal++] = pTarget; + + pTarget = gEntList.FindEntityByTarget( pTarget, STRING(GetEntityName()) ); + } + + pTarget = gEntList.FindEntityByClassname( NULL, "multi_manager" ); + while (pTarget && (m_iTotal < MS_MAX_TARGETS)) + { + if ( pTarget && pTarget->HasTarget(GetEntityName()) ) + m_rgEntities[m_iTotal++] = pTarget; + + pTarget = gEntList.FindEntityByClassname( pTarget, "multi_manager" ); + } + + m_spawnflags &= ~SF_MULTI_INIT; +} + + +//----------------------------------------------------------------------------- +// Purpose: Holds a value that can be added to and subtracted from. +//----------------------------------------------------------------------------- +class CMathCounter : public CLogicalEntity +{ + DECLARE_CLASS( CMathCounter, CLogicalEntity ); +private: + float m_flMin; // Minimum clamp value. If min and max are BOTH zero, no clamping is done. + float m_flMax; // Maximum clamp value. + bool m_bHitMin; // Set when we reach or go below our minimum value, cleared if we go above it again. + bool m_bHitMax; // Set when we reach or exceed our maximum value, cleared if we fall below it again. + + bool m_bDisabled; + + bool KeyValue(const char *szKeyName, const char *szValue); + void Spawn(void); + + int DrawDebugTextOverlays(void); + + void UpdateOutValue(CBaseEntity *pActivator, float fNewValue); + + // Inputs + void InputAdd( inputdata_t &inputdata ); + void InputDivide( inputdata_t &inputdata ); + void InputMultiply( inputdata_t &inputdata ); + void InputSetValue( inputdata_t &inputdata ); + void InputSetValueNoFire( inputdata_t &inputdata ); + void InputSubtract( inputdata_t &inputdata ); + void InputSetHitMax( inputdata_t &inputdata ); + void InputSetHitMin( inputdata_t &inputdata ); + void InputGetValue( inputdata_t &inputdata ); + void InputEnable( inputdata_t &inputdata ); + void InputDisable( inputdata_t &inputdata ); + + // Outputs + COutputFloat m_OutValue; + COutputFloat m_OnGetValue; // Used for polling the counter value. + COutputEvent m_OnHitMin; + COutputEvent m_OnHitMax; + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(math_counter, CMathCounter); + + +BEGIN_DATADESC( CMathCounter ) + + DEFINE_FIELD(m_bHitMax, FIELD_BOOLEAN), + DEFINE_FIELD(m_bHitMin, FIELD_BOOLEAN), + + // Keys + DEFINE_KEYFIELD(m_flMin, FIELD_FLOAT, "min"), + DEFINE_KEYFIELD(m_flMax, FIELD_FLOAT, "max"), + + DEFINE_KEYFIELD(m_bDisabled, FIELD_BOOLEAN, "StartDisabled" ), + + // Inputs + DEFINE_INPUTFUNC(FIELD_FLOAT, "Add", InputAdd), + DEFINE_INPUTFUNC(FIELD_FLOAT, "Divide", InputDivide), + DEFINE_INPUTFUNC(FIELD_FLOAT, "Multiply", InputMultiply), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SetValue", InputSetValue), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SetValueNoFire", InputSetValueNoFire), + DEFINE_INPUTFUNC(FIELD_FLOAT, "Subtract", InputSubtract), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SetHitMax", InputSetHitMax), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SetHitMin", InputSetHitMin), + DEFINE_INPUTFUNC(FIELD_VOID, "GetValue", InputGetValue), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), + + // Outputs + DEFINE_OUTPUT(m_OutValue, "OutValue"), + DEFINE_OUTPUT(m_OnHitMin, "OnHitMin"), + DEFINE_OUTPUT(m_OnHitMax, "OnHitMax"), + DEFINE_OUTPUT(m_OnGetValue, "OnGetValue"), + +END_DATADESC() + + + + +//----------------------------------------------------------------------------- +// Purpose: Handles key values from the BSP before spawn is called. +//----------------------------------------------------------------------------- +bool CMathCounter::KeyValue(const char *szKeyName, const char *szValue) +{ + // + // Set the initial value of the counter. + // + if (!stricmp(szKeyName, "startvalue")) + { + m_OutValue.Init(atoi(szValue)); + return(true); + } + + return(BaseClass::KeyValue(szKeyName, szValue)); +} + + +//----------------------------------------------------------------------------- +// Purpose: Called before spawning, after key values have been set. +//----------------------------------------------------------------------------- +void CMathCounter::Spawn( void ) +{ + // + // Make sure max and min are ordered properly or clamp won't work. + // + if (m_flMin > m_flMax) + { + float flTemp = m_flMax; + m_flMax = m_flMin; + m_flMin = flTemp; + } + + // + // Clamp initial value to within the valid range. + // + if ((m_flMin != 0) || (m_flMax != 0)) + { + float flStartValue = clamp(m_OutValue.Get(), m_flMin, m_flMax); + m_OutValue.Init(flStartValue); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Input : +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CMathCounter::DrawDebugTextOverlays( void ) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + + Q_snprintf(tempstr,sizeof(tempstr)," min value: %f", m_flMin); + EntityText(text_offset,tempstr,0); + text_offset++; + + Q_snprintf(tempstr,sizeof(tempstr)," max value: %f", m_flMax); + EntityText(text_offset,tempstr,0); + text_offset++; + + Q_snprintf(tempstr,sizeof(tempstr),"current value: %f", m_OutValue.Get()); + EntityText(text_offset,tempstr,0); + text_offset++; + + if( m_bDisabled ) + { + Q_snprintf(tempstr,sizeof(tempstr),"*DISABLED*"); + } + else + { + Q_snprintf(tempstr,sizeof(tempstr),"Enabled."); + } + EntityText(text_offset,tempstr,0); + text_offset++; + + } + return text_offset; +} + +//----------------------------------------------------------------------------- +// Change min/max +//----------------------------------------------------------------------------- +void CMathCounter::InputSetHitMax( inputdata_t &inputdata ) +{ + m_flMax = inputdata.value.Float(); + if ( m_flMax < m_flMin ) + { + m_flMin = m_flMax; + } + UpdateOutValue( inputdata.pActivator, m_OutValue.Get() ); +} + +void CMathCounter::InputSetHitMin( inputdata_t &inputdata ) +{ + m_flMin = inputdata.value.Float(); + if ( m_flMax < m_flMin ) + { + m_flMax = m_flMin; + } + UpdateOutValue( inputdata.pActivator, m_OutValue.Get() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for adding to the accumulator value. +// Input : Float value to add. +//----------------------------------------------------------------------------- +void CMathCounter::InputAdd( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring ADD because it is disabled\n", GetDebugName() ); + return; + } + + float fNewValue = m_OutValue.Get() + inputdata.value.Float(); + UpdateOutValue( inputdata.pActivator, fNewValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for multiplying the current value. +// Input : Float value to multiply the value by. +//----------------------------------------------------------------------------- +void CMathCounter::InputDivide( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring DIVIDE because it is disabled\n", GetDebugName() ); + return; + } + + if (inputdata.value.Float() != 0) + { + float fNewValue = m_OutValue.Get() / inputdata.value.Float(); + UpdateOutValue( inputdata.pActivator, fNewValue ); + } + else + { + DevMsg( 1, "LEVEL DESIGN ERROR: Divide by zero in math_value\n" ); + UpdateOutValue( inputdata.pActivator, m_OutValue.Get() ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for multiplying the current value. +// Input : Float value to multiply the value by. +//----------------------------------------------------------------------------- +void CMathCounter::InputMultiply( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring MULTIPLY because it is disabled\n", GetDebugName() ); + return; + } + + float fNewValue = m_OutValue.Get() * inputdata.value.Float(); + UpdateOutValue( inputdata.pActivator, fNewValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for updating the value. +// Input : Float value to set. +//----------------------------------------------------------------------------- +void CMathCounter::InputSetValue( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring SETVALUE because it is disabled\n", GetDebugName() ); + return; + } + + UpdateOutValue( inputdata.pActivator, inputdata.value.Float() ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for updating the value. +// Input : Float value to set. +//----------------------------------------------------------------------------- +void CMathCounter::InputSetValueNoFire( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring SETVALUENOFIRE because it is disabled\n", GetDebugName() ); + return; + } + + float flNewValue = inputdata.value.Float(); + if (( m_flMin != 0 ) || (m_flMax != 0 )) + { + flNewValue = clamp(flNewValue, m_flMin, m_flMax); + } + + m_OutValue.Init( flNewValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for subtracting from the current value. +// Input : Float value to subtract. +//----------------------------------------------------------------------------- +void CMathCounter::InputSubtract( inputdata_t &inputdata ) +{ + if( m_bDisabled ) + { + DevMsg("Math Counter %s ignoring SUBTRACT because it is disabled\n", GetDebugName() ); + return; + } + + float fNewValue = m_OutValue.Get() - inputdata.value.Float(); + UpdateOutValue( inputdata.pActivator, fNewValue ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathCounter::InputGetValue( inputdata_t &inputdata ) +{ + float flOutValue = m_OutValue.Get(); + m_OnGetValue.Set( flOutValue, inputdata.pActivator, inputdata.pCaller ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathCounter::InputEnable( inputdata_t &inputdata ) +{ + m_bDisabled = false; +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CMathCounter::InputDisable( inputdata_t &inputdata ) +{ + m_bDisabled = true; +} + +//----------------------------------------------------------------------------- +// Purpose: Sets the value to the new value, clamping and firing the output value. +// Input : fNewValue - Value to set. +//----------------------------------------------------------------------------- +void CMathCounter::UpdateOutValue(CBaseEntity *pActivator, float fNewValue) +{ + if ((m_flMin != 0) || (m_flMax != 0)) + { + // + // Fire an output any time we reach or exceed our maximum value. + // + if ( fNewValue >= m_flMax ) + { + if ( !m_bHitMax ) + { + m_bHitMax = true; + m_OnHitMax.FireOutput( pActivator, this ); + } + } + else + { + m_bHitMax = false; + } + + // + // Fire an output any time we reach or go below our minimum value. + // + if ( fNewValue <= m_flMin ) + { + if ( !m_bHitMin ) + { + m_bHitMin = true; + m_OnHitMin.FireOutput( pActivator, this ); + } + } + else + { + m_bHitMin = false; + } + + fNewValue = clamp(fNewValue, m_flMin, m_flMax); + } + + m_OutValue.Set(fNewValue, pActivator, this); +} + + + +//----------------------------------------------------------------------------- +// Purpose: Compares a single string input to up to 16 case values, firing an +// output corresponding to the case value that matched, or a default +// output if the input value didn't match any of the case values. +// +// This can also be used to fire a random output from a set of outputs. +//----------------------------------------------------------------------------- +#define MAX_LOGIC_CASES 16 + +class CLogicCase : public CLogicalEntity +{ + DECLARE_CLASS( CLogicCase, CLogicalEntity ); +private: + string_t m_nCase[MAX_LOGIC_CASES]; + + int m_nShuffleCases; + int m_nLastShuffleCase; + unsigned char m_uchShuffleCaseMap[MAX_LOGIC_CASES]; + + void Spawn(void); + + int BuildCaseMap(unsigned char *puchMap); + + // Inputs + void InputValue( inputdata_t &inputdata ); + void InputPickRandom( inputdata_t &inputdata ); + void InputPickRandomShuffle( inputdata_t &inputdata ); + + // Outputs + COutputEvent m_OnCase[MAX_LOGIC_CASES]; // Fired when the input value matches one of the case values. + COutputVariant m_OnDefault; // Fired when no match was found. + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_case, CLogicCase); + + +BEGIN_DATADESC( CLogicCase ) + +// Silence, Classcheck! +// DEFINE_ARRAY( m_nCase, FIELD_STRING, MAX_LOGIC_CASES ), + + // Keys + DEFINE_KEYFIELD(m_nCase[0], FIELD_STRING, "Case01"), + DEFINE_KEYFIELD(m_nCase[1], FIELD_STRING, "Case02"), + DEFINE_KEYFIELD(m_nCase[2], FIELD_STRING, "Case03"), + DEFINE_KEYFIELD(m_nCase[3], FIELD_STRING, "Case04"), + DEFINE_KEYFIELD(m_nCase[4], FIELD_STRING, "Case05"), + DEFINE_KEYFIELD(m_nCase[5], FIELD_STRING, "Case06"), + DEFINE_KEYFIELD(m_nCase[6], FIELD_STRING, "Case07"), + DEFINE_KEYFIELD(m_nCase[7], FIELD_STRING, "Case08"), + DEFINE_KEYFIELD(m_nCase[8], FIELD_STRING, "Case09"), + DEFINE_KEYFIELD(m_nCase[9], FIELD_STRING, "Case10"), + DEFINE_KEYFIELD(m_nCase[10], FIELD_STRING, "Case11"), + DEFINE_KEYFIELD(m_nCase[11], FIELD_STRING, "Case12"), + DEFINE_KEYFIELD(m_nCase[12], FIELD_STRING, "Case13"), + DEFINE_KEYFIELD(m_nCase[13], FIELD_STRING, "Case14"), + DEFINE_KEYFIELD(m_nCase[14], FIELD_STRING, "Case15"), + DEFINE_KEYFIELD(m_nCase[15], FIELD_STRING, "Case16"), + + DEFINE_FIELD( m_nShuffleCases, FIELD_INTEGER ), + DEFINE_FIELD( m_nLastShuffleCase, FIELD_INTEGER ), + DEFINE_ARRAY( m_uchShuffleCaseMap, FIELD_CHARACTER, MAX_LOGIC_CASES ), + + // Inputs + DEFINE_INPUTFUNC(FIELD_INPUT, "InValue", InputValue), + DEFINE_INPUTFUNC(FIELD_VOID, "PickRandom", InputPickRandom), + DEFINE_INPUTFUNC(FIELD_VOID, "PickRandomShuffle", InputPickRandomShuffle), + + // Outputs + DEFINE_OUTPUT(m_OnCase[0], "OnCase01"), + DEFINE_OUTPUT(m_OnCase[1], "OnCase02"), + DEFINE_OUTPUT(m_OnCase[2], "OnCase03"), + DEFINE_OUTPUT(m_OnCase[3], "OnCase04"), + DEFINE_OUTPUT(m_OnCase[4], "OnCase05"), + DEFINE_OUTPUT(m_OnCase[5], "OnCase06"), + DEFINE_OUTPUT(m_OnCase[6], "OnCase07"), + DEFINE_OUTPUT(m_OnCase[7], "OnCase08"), + DEFINE_OUTPUT(m_OnCase[8], "OnCase09"), + DEFINE_OUTPUT(m_OnCase[9], "OnCase10"), + DEFINE_OUTPUT(m_OnCase[10], "OnCase11"), + DEFINE_OUTPUT(m_OnCase[11], "OnCase12"), + DEFINE_OUTPUT(m_OnCase[12], "OnCase13"), + DEFINE_OUTPUT(m_OnCase[13], "OnCase14"), + DEFINE_OUTPUT(m_OnCase[14], "OnCase15"), + DEFINE_OUTPUT(m_OnCase[15], "OnCase16"), + + DEFINE_OUTPUT(m_OnDefault, "OnDefault"), + +END_DATADESC() + + + + +//----------------------------------------------------------------------------- +// Purpose: Called before spawning, after key values have been set. +//----------------------------------------------------------------------------- +void CLogicCase::Spawn( void ) +{ + m_nLastShuffleCase = -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: Evaluates the new input value, firing the appropriate OnCaseX output +// if the input value matches one of the "CaseX" keys. +// Input : Value - Variant value to compare against the values of the case fields. +// We use a variant so that we can convert any input type to a string. +//----------------------------------------------------------------------------- +void CLogicCase::InputValue( inputdata_t &inputdata ) +{ + const char *pszValue = inputdata.value.String(); + for (int i = 0; i < MAX_LOGIC_CASES; i++) + { + if ((m_nCase[i] != NULL_STRING) && !stricmp(STRING(m_nCase[i]), pszValue)) + { + m_OnCase[i].FireOutput( inputdata.pActivator, this ); + return; + } + } + + m_OnDefault.Set( inputdata.value, inputdata.pActivator, this ); +} + + +//----------------------------------------------------------------------------- +// Count the number of valid cases, building a packed array +// that maps 0..NumCases to the actual CaseX values. +// +// This allows our zany mappers to set up cases sparsely if they desire. +// NOTE: assumes pnMap points to an array of MAX_LOGIC_CASES +//----------------------------------------------------------------------------- +int CLogicCase::BuildCaseMap(unsigned char *puchCaseMap) +{ + memset(puchCaseMap, 0, sizeof(unsigned char) * MAX_LOGIC_CASES); + + int nNumCases = 0; + for (int i = 0; i < MAX_LOGIC_CASES; i++) + { + if (m_OnCase[i].NumberOfElements() > 0) + { + puchCaseMap[nNumCases] = (unsigned char)i; + nNumCases++; + } + } + + return nNumCases; +} + + +//----------------------------------------------------------------------------- +// Purpose: Makes the case statement choose a case at random. +//----------------------------------------------------------------------------- +void CLogicCase::InputPickRandom( inputdata_t &inputdata ) +{ + unsigned char uchCaseMap[MAX_LOGIC_CASES]; + int nNumCases = BuildCaseMap( uchCaseMap ); + + // + // Choose a random case from the ones that were set up by the level designer. + // + if ( nNumCases > 0 ) + { + int nRandom = random->RandomInt(0, nNumCases - 1); + int nCase = (unsigned char)uchCaseMap[nRandom]; + + Assert(nCase < MAX_LOGIC_CASES); + + if (nCase < MAX_LOGIC_CASES) + { + m_OnCase[nCase].FireOutput( inputdata.pActivator, this ); + } + } + else + { + DevMsg( 1, "Firing PickRandom input on logic_case %s with no cases set up\n", GetDebugName() ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Makes the case statement choose a case at random. +//----------------------------------------------------------------------------- +void CLogicCase::InputPickRandomShuffle( inputdata_t &inputdata ) +{ + int nAvoidCase = -1; + int nCaseCount = m_nShuffleCases; + + if ( nCaseCount == 0 ) + { + // Starting a new shuffle batch. + nCaseCount = m_nShuffleCases = BuildCaseMap( m_uchShuffleCaseMap ); + + if ( ( m_nShuffleCases > 1 ) && ( m_nLastShuffleCase != -1 ) ) + { + // Remove the previously picked case from the case map for this pick only. + // This avoids repeats across shuffle batch boundaries. + nAvoidCase = m_nLastShuffleCase; + + for (int i = 0; i < m_nShuffleCases; i++ ) + { + if ( m_uchShuffleCaseMap[i] == nAvoidCase ) + { + unsigned char uchSwap = m_uchShuffleCaseMap[i]; + m_uchShuffleCaseMap[i] = m_uchShuffleCaseMap[nCaseCount - 1]; + m_uchShuffleCaseMap[nCaseCount - 1] = uchSwap; + nCaseCount--; + break; + } + } + } + } + + // + // Choose a random case from the ones that were set up by the level designer. + // Never repeat a case within a shuffle batch, nor consecutively across batches. + // + if ( nCaseCount > 0 ) + { + int nRandom = random->RandomInt( 0, nCaseCount - 1 ); + + int nCase = m_uchShuffleCaseMap[nRandom]; + Assert(nCase < MAX_LOGIC_CASES); + + if (nCase < MAX_LOGIC_CASES) + { + m_OnCase[nCase].FireOutput( inputdata.pActivator, this ); + } + + m_uchShuffleCaseMap[nRandom] = m_uchShuffleCaseMap[m_nShuffleCases - 1]; + m_nShuffleCases--; + + m_nLastShuffleCase = nCase; + } + else + { + DevMsg( 1, "Firing PickRandom input on logic_case %s with no cases set up\n", GetDebugName() ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Compares a floating point input to a predefined value, firing an +// output to indicate the result of the comparison. +//----------------------------------------------------------------------------- +class CLogicCompare : public CLogicalEntity +{ + DECLARE_CLASS( CLogicCompare, CLogicalEntity ); + +public: + int DrawDebugTextOverlays(void); + +private: + // Inputs + void InputSetValue( inputdata_t &inputdata ); + void InputSetValueCompare( inputdata_t &inputdata ); + void InputSetCompareValue( inputdata_t &inputdata ); + void InputCompare( inputdata_t &inputdata ); + + void DoCompare(CBaseEntity *pActivator, float flInValue); + + float m_flInValue; // Place to hold the last input value for a recomparison. + float m_flCompareValue; // The value to compare the input value against. + + // Outputs + COutputFloat m_OnLessThan; // Fired when the input value is less than the compare value. + COutputFloat m_OnEqualTo; // Fired when the input value is equal to the compare value. + COutputFloat m_OnNotEqualTo; // Fired when the input value is not equal to the compare value. + COutputFloat m_OnGreaterThan; // Fired when the input value is greater than the compare value. + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_compare, CLogicCompare); + + +BEGIN_DATADESC( CLogicCompare ) + + // Keys + DEFINE_KEYFIELD(m_flCompareValue, FIELD_FLOAT, "CompareValue"), + DEFINE_KEYFIELD(m_flInValue, FIELD_FLOAT, "InitialValue"), + + // Inputs + DEFINE_INPUTFUNC(FIELD_FLOAT, "SetValue", InputSetValue), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SetValueCompare", InputSetValueCompare), + DEFINE_INPUTFUNC(FIELD_FLOAT, "SetCompareValue", InputSetCompareValue), + DEFINE_INPUTFUNC(FIELD_VOID, "Compare", InputCompare), + + // Outputs + DEFINE_OUTPUT(m_OnEqualTo, "OnEqualTo"), + DEFINE_OUTPUT(m_OnNotEqualTo, "OnNotEqualTo"), + DEFINE_OUTPUT(m_OnGreaterThan, "OnGreaterThan"), + DEFINE_OUTPUT(m_OnLessThan, "OnLessThan"), + +END_DATADESC() + + + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for a new input value without performing a comparison. +//----------------------------------------------------------------------------- +void CLogicCompare::InputSetValue( inputdata_t &inputdata ) +{ + m_flInValue = inputdata.value.Float(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for a setting a new value and doing the comparison. +//----------------------------------------------------------------------------- +void CLogicCompare::InputSetValueCompare( inputdata_t &inputdata ) +{ + m_flInValue = inputdata.value.Float(); + DoCompare( inputdata.pActivator, m_flInValue ); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for a new input value without performing a comparison. +//----------------------------------------------------------------------------- +void CLogicCompare::InputSetCompareValue( inputdata_t &inputdata ) +{ + m_flCompareValue = inputdata.value.Float(); +} + +//----------------------------------------------------------------------------- +// Purpose: Input handler for forcing a recompare of the last input value. +//----------------------------------------------------------------------------- +void CLogicCompare::InputCompare( inputdata_t &inputdata ) +{ + DoCompare( inputdata.pActivator, m_flInValue ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Compares the input value to the compare value, firing the appropriate +// output(s) based on the comparison result. +// Input : flInValue - Value to compare against the comparison value. +//----------------------------------------------------------------------------- +void CLogicCompare::DoCompare(CBaseEntity *pActivator, float flInValue) +{ + if (flInValue == m_flCompareValue) + { + m_OnEqualTo.Set(flInValue, pActivator, this); + } + else + { + m_OnNotEqualTo.Set(flInValue, pActivator, this); + + if (flInValue > m_flCompareValue) + { + m_OnGreaterThan.Set(flInValue, pActivator, this); + } + else + { + m_OnLessThan.Set(flInValue, pActivator, this); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Draw any debug text overlays +// Output : Current text offset from the top +//----------------------------------------------------------------------------- +int CLogicCompare::DrawDebugTextOverlays( void ) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + + // print duration + Q_snprintf(tempstr,sizeof(tempstr)," Initial Value: %f", m_flInValue); + EntityText(text_offset,tempstr,0); + text_offset++; + + // print hold time + Q_snprintf(tempstr,sizeof(tempstr)," Compare Value: %f", m_flCompareValue); + EntityText(text_offset,tempstr,0); + text_offset++; + } + return text_offset; +} + +//----------------------------------------------------------------------------- +// Purpose: Tests a boolean value, firing an output to indicate whether the +// value was true or false. +//----------------------------------------------------------------------------- +class CLogicBranch : public CLogicalEntity +{ + DECLARE_CLASS( CLogicBranch, CLogicalEntity ); + +public: + + void UpdateOnRemove(); + + void AddLogicBranchListener( CBaseEntity *pEntity ); + inline bool GetLogicBranchState(); + virtual int DrawDebugTextOverlays( void ); + +private: + + enum LogicBranchFire_t + { + LOGIC_BRANCH_FIRE, + LOGIC_BRANCH_NO_FIRE, + }; + + // Inputs + void InputSetValue( inputdata_t &inputdata ); + void InputSetValueTest( inputdata_t &inputdata ); + void InputToggle( inputdata_t &inputdata ); + void InputToggleTest( inputdata_t &inputdata ); + void InputTest( inputdata_t &inputdata ); + + void UpdateValue(bool bNewValue, CBaseEntity *pActivator, LogicBranchFire_t eFire); + + bool m_bInValue; // Place to hold the last input value for a future test. + + CUtlVector m_Listeners; // A list of logic_branch_listeners that are monitoring us. + + // Outputs + COutputEvent m_OnTrue; // Fired when the value is true. + COutputEvent m_OnFalse; // Fired when the value is false. + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_branch, CLogicBranch); + + +BEGIN_DATADESC( CLogicBranch ) + + // Keys + DEFINE_KEYFIELD(m_bInValue, FIELD_BOOLEAN, "InitialValue"), + + DEFINE_UTLVECTOR( m_Listeners, FIELD_EHANDLE ), + + // Inputs + DEFINE_INPUTFUNC(FIELD_BOOLEAN, "SetValue", InputSetValue), + DEFINE_INPUTFUNC(FIELD_BOOLEAN, "SetValueTest", InputSetValueTest), + DEFINE_INPUTFUNC(FIELD_VOID, "Toggle", InputToggle), + DEFINE_INPUTFUNC(FIELD_VOID, "ToggleTest", InputToggleTest), + DEFINE_INPUTFUNC(FIELD_VOID, "Test", InputTest), + + // Outputs + DEFINE_OUTPUT(m_OnTrue, "OnTrue"), + DEFINE_OUTPUT(m_OnFalse, "OnFalse"), + +END_DATADESC() + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicBranch::UpdateOnRemove() +{ + for ( int i = 0; i < m_Listeners.Count(); i++ ) + { + CBaseEntity *pEntity = m_Listeners.Element( i ).Get(); + if ( pEntity ) + { + g_EventQueue.AddEvent( this, "_OnLogicBranchRemoved", 0, this, this ); + } + } + + BaseClass::UpdateOnRemove(); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler to set a new input value without firing outputs. +// Input : Boolean value to set. +//----------------------------------------------------------------------------- +void CLogicBranch::InputSetValue( inputdata_t &inputdata ) +{ + UpdateValue( inputdata.value.Bool(), inputdata.pActivator, LOGIC_BRANCH_NO_FIRE ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler to set a new input value and fire appropriate outputs. +// Input : Boolean value to set. +//----------------------------------------------------------------------------- +void CLogicBranch::InputSetValueTest( inputdata_t &inputdata ) +{ + UpdateValue( inputdata.value.Bool(), inputdata.pActivator, LOGIC_BRANCH_FIRE ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for toggling the boolean value without firing outputs. +//----------------------------------------------------------------------------- +void CLogicBranch::InputToggle( inputdata_t &inputdata ) +{ + UpdateValue( !m_bInValue, inputdata.pActivator, LOGIC_BRANCH_NO_FIRE ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for toggling the boolean value and then firing the +// appropriate output based on the new value. +//----------------------------------------------------------------------------- +void CLogicBranch::InputToggleTest( inputdata_t &inputdata ) +{ + UpdateValue( !m_bInValue, inputdata.pActivator, LOGIC_BRANCH_FIRE ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Input handler for forcing a test of the last input value. +//----------------------------------------------------------------------------- +void CLogicBranch::InputTest( inputdata_t &inputdata ) +{ + UpdateValue( m_bInValue, inputdata.pActivator, LOGIC_BRANCH_FIRE ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Tests the last input value, firing the appropriate output based on +// the test result. +// Input : bInValue - +//----------------------------------------------------------------------------- +void CLogicBranch::UpdateValue( bool bNewValue, CBaseEntity *pActivator, LogicBranchFire_t eFire ) +{ + if ( m_bInValue != bNewValue ) + { + m_bInValue = bNewValue; + + for ( int i = 0; i < m_Listeners.Count(); i++ ) + { + CBaseEntity *pEntity = m_Listeners.Element( i ).Get(); + if ( pEntity ) + { + g_EventQueue.AddEvent( pEntity, "_OnLogicBranchChanged", 0, this, this ); + } + } + } + + if ( eFire == LOGIC_BRANCH_FIRE ) + { + if ( m_bInValue ) + { + m_OnTrue.FireOutput( pActivator, this ); + } + else + { + m_OnFalse.FireOutput( pActivator, this ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Accessor for logic_branchlist to test the value of the branch on demand. +//----------------------------------------------------------------------------- +bool CLogicBranch::GetLogicBranchState() +{ + return m_bInValue; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicBranch::AddLogicBranchListener( CBaseEntity *pEntity ) +{ + if ( m_Listeners.Find( pEntity ) == -1 ) + { + m_Listeners.AddToTail( pEntity ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CLogicBranch::DrawDebugTextOverlays( void ) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + + // print refire time + Q_snprintf( tempstr, sizeof(tempstr), "Branch value: %s", (m_bInValue) ? "TRUE" : "FALSE" ); + EntityText( text_offset, tempstr, 0 ); + text_offset++; + } + + return text_offset; +} + +//----------------------------------------------------------------------------- +// Purpose: Autosaves when triggered +//----------------------------------------------------------------------------- +class CLogicAutosave : public CLogicalEntity +{ + DECLARE_CLASS( CLogicAutosave, CLogicalEntity ); + +protected: + // Inputs + void InputSave( inputdata_t &inputdata ); + void InputSaveDangerous( inputdata_t &inputdata ); + void InputSetMinHitpointsThreshold( inputdata_t &inputdata ); + + DECLARE_DATADESC(); + bool m_bForceNewLevelUnit; + int m_minHitPoints; + int m_minHitPointsToCommit; +}; + +LINK_ENTITY_TO_CLASS(logic_autosave, CLogicAutosave); + +BEGIN_DATADESC( CLogicAutosave ) + DEFINE_KEYFIELD( m_bForceNewLevelUnit, FIELD_BOOLEAN, "NewLevelUnit" ), + DEFINE_KEYFIELD( m_minHitPoints, FIELD_INTEGER, "MinimumHitPoints" ), + DEFINE_KEYFIELD( m_minHitPointsToCommit, FIELD_INTEGER, "MinHitPointsToCommit" ), + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "Save", InputSave ), + DEFINE_INPUTFUNC( FIELD_FLOAT, "SaveDangerous", InputSaveDangerous ), + DEFINE_INPUTFUNC( FIELD_INTEGER, "SetMinHitpointsThreshold", InputSetMinHitpointsThreshold ), +END_DATADESC() + +//----------------------------------------------------------------------------- +// Purpose: Save! +//----------------------------------------------------------------------------- +void CLogicAutosave::InputSave( inputdata_t &inputdata ) +{ + if ( m_bForceNewLevelUnit ) + { + engine->ClearSaveDir(); + } + + engine->ServerCommand( "autosave\n" ); +} + +//----------------------------------------------------------------------------- +// Purpose: Save safely! +//----------------------------------------------------------------------------- +void CLogicAutosave::InputSaveDangerous( inputdata_t &inputdata ) +{ + CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); + + if ( g_ServerGameDLL.m_fAutoSaveDangerousTime != 0.0f && g_ServerGameDLL.m_fAutoSaveDangerousTime >= gpGlobals->curtime ) + { + // A previous dangerous auto save was waiting to become safe + + if ( pPlayer->GetDeathTime() == 0.0f || pPlayer->GetDeathTime() > gpGlobals->curtime ) + { + // The player isn't dead, so make the dangerous auto save safe + engine->ServerCommand( "autosavedangerousissafe\n" ); + } + } + + if ( m_bForceNewLevelUnit ) + { + engine->ClearSaveDir(); + } + + if ( pPlayer->GetHealth() >= m_minHitPoints ) + { + engine->ServerCommand( "autosavedangerous\n" ); + g_ServerGameDLL.m_fAutoSaveDangerousTime = gpGlobals->curtime + inputdata.value.Float(); + + // Player must have this much health when we go to commit, or we don't commit. + g_ServerGameDLL.m_fAutoSaveDangerousMinHealthToCommit = m_minHitPointsToCommit; + } +} + +//----------------------------------------------------------------------------- +// Purpose: Autosaves when triggered +//----------------------------------------------------------------------------- +class CLogicActiveAutosave : public CLogicAutosave +{ + DECLARE_CLASS( CLogicActiveAutosave, CLogicAutosave ); + + void InputEnable( inputdata_t &inputdata ) + { + m_flStartTime = -1; + SetThink( &CLogicActiveAutosave::SaveThink ); + SetNextThink( gpGlobals->curtime ); + } + + void InputDisable( inputdata_t &inputdata ) + { + SetThink( NULL ); + } + + void SaveThink() + { + CBasePlayer *pPlayer = UTIL_GetLocalPlayer(); + if ( pPlayer ) + { + if ( m_flStartTime < 0 ) + { + if ( pPlayer->GetHealth() <= m_minHitPoints ) + { + m_flStartTime = gpGlobals->curtime; + } + } + else + { + if ( pPlayer->GetHealth() >= m_TriggerHitPoints ) + { + inputdata_t inputdata; + DevMsg( 2, "logic_active_autosave (%s, %d) triggered\n", STRING( GetEntityName() ), entindex() ); + if ( !m_flDangerousTime ) + { + InputSave( inputdata ); + } + else + { + inputdata.value.SetFloat( m_flDangerousTime ); + InputSaveDangerous( inputdata ); + } + m_flStartTime = -1; + } + else if ( m_flTimeToTrigger > 0 && gpGlobals->curtime - m_flStartTime > m_flTimeToTrigger ) + { + m_flStartTime = -1; + } + } + } + + float thinkInterval = ( m_flStartTime < 0 ) ? 1.0 : 0.5; + SetNextThink( gpGlobals->curtime + thinkInterval ); + } + + DECLARE_DATADESC(); + + int m_TriggerHitPoints; + float m_flTimeToTrigger; + float m_flStartTime; + float m_flDangerousTime; +}; + +LINK_ENTITY_TO_CLASS(logic_active_autosave, CLogicActiveAutosave); + +BEGIN_DATADESC( CLogicActiveAutosave ) + DEFINE_KEYFIELD( m_TriggerHitPoints, FIELD_INTEGER, "TriggerHitPoints" ), + DEFINE_KEYFIELD( m_flTimeToTrigger, FIELD_FLOAT, "TimeToTrigger" ), + DEFINE_KEYFIELD( m_flDangerousTime, FIELD_FLOAT, "DangerousTime" ), + DEFINE_FIELD( m_flStartTime, FIELD_TIME ), + DEFINE_THINKFUNC( SaveThink ), + DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), + DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: Keyfield set func +//----------------------------------------------------------------------------- +void CLogicAutosave::InputSetMinHitpointsThreshold( inputdata_t &inputdata ) +{ + int setTo = inputdata.value.Int(); + AssertMsg1(setTo >= 0 && setTo <= 100, "Tried to set autosave MinHitpointsThreshold to %d!\n", setTo); + m_minHitPoints = setTo; +} + +// Finds the named physics object. If no name, returns the world +// If a name is specified and an object not found - errors are reported +IPhysicsObject *FindPhysicsObjectByNameOrWorld( string_t name, CBaseEntity *pErrorEntity ) +{ + if ( !name ) + return g_PhysWorldObject; + + IPhysicsObject *pPhysics = FindPhysicsObjectByName( name.ToCStr(), pErrorEntity ); + if ( !pPhysics ) + { + DevWarning("%s: can't find %s\n", pErrorEntity->GetClassname(), name.ToCStr()); + } + return pPhysics; +} + +class CLogicCollisionPair : public CLogicalEntity +{ + DECLARE_CLASS( CLogicCollisionPair, CLogicalEntity ); +public: + + void EnableCollisions( bool bEnable ) + { + IPhysicsObject *pPhysics0 = FindPhysicsObjectByNameOrWorld( m_nameAttach1, this ); + IPhysicsObject *pPhysics1 = FindPhysicsObjectByNameOrWorld( m_nameAttach2, this ); + + // need two different objects to do anything + if ( pPhysics0 && pPhysics1 && pPhysics0 != pPhysics1 ) + { + m_disabled = !bEnable; + m_succeeded = true; + if ( bEnable ) + { + PhysEnableEntityCollisions( pPhysics0, pPhysics1 ); + } + else + { + PhysDisableEntityCollisions( pPhysics0, pPhysics1 ); + } + } + else + { + m_succeeded = false; + } + } + + void Activate( void ) + { + if ( m_disabled ) + { + EnableCollisions( false ); + } + BaseClass::Activate(); + } + + void InputDisableCollisions( inputdata_t &inputdata ) + { + if ( m_succeeded && m_disabled ) + return; + EnableCollisions( false ); + } + + void InputEnableCollisions( inputdata_t &inputdata ) + { + if ( m_succeeded && !m_disabled ) + return; + EnableCollisions( true ); + } + // If Activate() becomes PostSpawn() + //void OnRestore() { Activate(); } + + DECLARE_DATADESC(); + +private: + string_t m_nameAttach1; + string_t m_nameAttach2; + bool m_disabled; + bool m_succeeded; +}; + +BEGIN_DATADESC( CLogicCollisionPair ) + DEFINE_KEYFIELD( m_nameAttach1, FIELD_STRING, "attach1" ), + DEFINE_KEYFIELD( m_nameAttach2, FIELD_STRING, "attach2" ), + DEFINE_KEYFIELD( m_disabled, FIELD_BOOLEAN, "startdisabled" ), + DEFINE_FIELD( m_succeeded, FIELD_BOOLEAN ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_VOID, "DisableCollisions", InputDisableCollisions ), + DEFINE_INPUTFUNC( FIELD_VOID, "EnableCollisions", InputEnableCollisions ), +END_DATADESC() + +LINK_ENTITY_TO_CLASS( logic_collision_pair, CLogicCollisionPair ); + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +#define MAX_LOGIC_BRANCH_NAMES 16 + +class CLogicBranchList : public CLogicalEntity +{ + DECLARE_CLASS( CLogicBranchList, CLogicalEntity ); + + virtual void Spawn(); + virtual void Activate(); + virtual int DrawDebugTextOverlays( void ); + +private: + + enum LogicBranchListenerLastState_t + { + LOGIC_BRANCH_LISTENER_NOT_INIT = 0, + LOGIC_BRANCH_LISTENER_ALL_TRUE, + LOGIC_BRANCH_LISTENER_ALL_FALSE, + LOGIC_BRANCH_LISTENER_MIXED, + }; + + void DoTest( CBaseEntity *pActivator ); + + string_t m_nLogicBranchNames[MAX_LOGIC_BRANCH_NAMES]; + CUtlVector m_LogicBranchList; + LogicBranchListenerLastState_t m_eLastState; + + // Inputs + void Input_OnLogicBranchRemoved( inputdata_t &inputdata ); + void Input_OnLogicBranchChanged( inputdata_t &inputdata ); + void InputTest( inputdata_t &inputdata ); + + // Outputs + COutputEvent m_OnAllTrue; // Fired when all the registered logic_branches become true. + COutputEvent m_OnAllFalse; // Fired when all the registered logic_branches become false. + COutputEvent m_OnMixed; // Fired when one of the registered logic branches changes, but not all are true or false. + + DECLARE_DATADESC(); +}; + +LINK_ENTITY_TO_CLASS(logic_branch_listener, CLogicBranchList); + + +BEGIN_DATADESC( CLogicBranchList ) + + // Silence, classcheck! + //DEFINE_ARRAY( m_nLogicBranchNames, FIELD_STRING, MAX_LOGIC_BRANCH_NAMES ), + + // Keys + DEFINE_KEYFIELD( m_nLogicBranchNames[0], FIELD_STRING, "Branch01" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[1], FIELD_STRING, "Branch02" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[2], FIELD_STRING, "Branch03" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[3], FIELD_STRING, "Branch04" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[4], FIELD_STRING, "Branch05" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[5], FIELD_STRING, "Branch06" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[6], FIELD_STRING, "Branch07" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[7], FIELD_STRING, "Branch08" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[8], FIELD_STRING, "Branch09" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[9], FIELD_STRING, "Branch10" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[10], FIELD_STRING, "Branch11" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[11], FIELD_STRING, "Branch12" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[12], FIELD_STRING, "Branch13" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[13], FIELD_STRING, "Branch14" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[14], FIELD_STRING, "Branch15" ), + DEFINE_KEYFIELD( m_nLogicBranchNames[15], FIELD_STRING, "Branch16" ), + + DEFINE_UTLVECTOR( m_LogicBranchList, FIELD_EHANDLE ), + + DEFINE_FIELD( m_eLastState, FIELD_INTEGER ), + + // Inputs + DEFINE_INPUTFUNC( FIELD_INPUT, "Test", InputTest ), + DEFINE_INPUTFUNC( FIELD_INPUT, "_OnLogicBranchChanged", Input_OnLogicBranchChanged ), + DEFINE_INPUTFUNC( FIELD_INPUT, "_OnLogicBranchRemoved", Input_OnLogicBranchRemoved ), + + // Outputs + DEFINE_OUTPUT( m_OnAllTrue, "OnAllTrue" ), + DEFINE_OUTPUT( m_OnAllFalse, "OnAllFalse" ), + DEFINE_OUTPUT( m_OnMixed, "OnMixed" ), + +END_DATADESC() + + +//----------------------------------------------------------------------------- +// Purpose: Called before spawning, after key values have been set. +//----------------------------------------------------------------------------- +void CLogicBranchList::Spawn( void ) +{ +} + + +//----------------------------------------------------------------------------- +// Finds all the logic_branches that we are monitoring and register ourselves with them. +//----------------------------------------------------------------------------- +void CLogicBranchList::Activate( void ) +{ + for ( int i = 0; i < MAX_LOGIC_BRANCH_NAMES; i++ ) + { + CBaseEntity *pEntity = NULL; + while ( ( pEntity = gEntList.FindEntityGeneric( pEntity, STRING( m_nLogicBranchNames[i] ), this ) ) != NULL ) + { + if ( FClassnameIs( pEntity, "logic_branch" ) ) + { + CLogicBranch *pBranch = (CLogicBranch *)pEntity; + pBranch->AddLogicBranchListener( this ); + m_LogicBranchList.AddToTail( pBranch ); + } + else + { + DevWarning( "logic_branchlist %s refers to entity %s, which is not a logic_branch\n", GetDebugName(), pEntity->GetDebugName() ); + } + } + } + + BaseClass::Activate(); +} + + +//----------------------------------------------------------------------------- +// Called when a monitored logic branch is deleted from the world, since that +// might affect our final result. +//----------------------------------------------------------------------------- +void CLogicBranchList::Input_OnLogicBranchRemoved( inputdata_t &inputdata ) +{ + int nIndex = m_LogicBranchList.Find( inputdata.pActivator ); + if ( nIndex != -1 ) + { + m_LogicBranchList.FastRemove( nIndex ); + } + + // See if this logic_branch's deletion affects the final result. + DoTest( inputdata.pActivator ); +} + + +//----------------------------------------------------------------------------- +// Called when the value of a monitored logic branch changes. +//----------------------------------------------------------------------------- +void CLogicBranchList::Input_OnLogicBranchChanged( inputdata_t &inputdata ) +{ + DoTest( inputdata.pActivator ); +} + + +//----------------------------------------------------------------------------- +// Input handler to manually test the monitored logic branches and fire the +// appropriate output. +//----------------------------------------------------------------------------- +void CLogicBranchList::InputTest( inputdata_t &inputdata ) +{ + // Force an output. + m_eLastState = LOGIC_BRANCH_LISTENER_NOT_INIT; + + DoTest( inputdata.pActivator ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CLogicBranchList::DoTest( CBaseEntity *pActivator ) +{ + bool bOneTrue = false; + bool bOneFalse = false; + + for ( int i = 0; i < m_LogicBranchList.Count(); i++ ) + { + CLogicBranch *pBranch = (CLogicBranch *)m_LogicBranchList.Element( i ).Get(); + if ( pBranch && pBranch->GetLogicBranchState() ) + { + bOneTrue = true; + } + else + { + bOneFalse = true; + } + } + + // Only fire the output if the new result differs from the last result. + if ( bOneTrue && !bOneFalse ) + { + if ( m_eLastState != LOGIC_BRANCH_LISTENER_ALL_TRUE ) + { + m_OnAllTrue.FireOutput( pActivator, this ); + m_eLastState = LOGIC_BRANCH_LISTENER_ALL_TRUE; + } + } + else if ( bOneFalse && !bOneTrue ) + { + if ( m_eLastState != LOGIC_BRANCH_LISTENER_ALL_FALSE ) + { + m_OnAllFalse.FireOutput( pActivator, this ); + m_eLastState = LOGIC_BRANCH_LISTENER_ALL_FALSE; + } + } + else + { + if ( m_eLastState != LOGIC_BRANCH_LISTENER_MIXED ) + { + m_OnMixed.FireOutput( pActivator, this ); + m_eLastState = LOGIC_BRANCH_LISTENER_MIXED; + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +int CLogicBranchList::DrawDebugTextOverlays( void ) +{ + int text_offset = BaseClass::DrawDebugTextOverlays(); + + if (m_debugOverlays & OVERLAY_TEXT_BIT) + { + char tempstr[512]; + + for ( int i = 0; i < m_LogicBranchList.Count(); i++ ) + { + CLogicBranch *pBranch = (CLogicBranch *)m_LogicBranchList.Element( i ).Get(); + if ( pBranch ) + { + Q_snprintf( tempstr, sizeof(tempstr), "Branch (%s): %s", STRING(pBranch->GetEntityName()), (pBranch->GetLogicBranchState()) ? "TRUE" : "FALSE" ); + EntityText( text_offset, tempstr, 0 ); + text_offset++; + } + } + } + + return text_offset; +} -- cgit v1.2.3