//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ==== // // Purpose: Implements buttons. // //============================================================================= #include "cbase.h" #include "doors.h" #include "ndebugoverlay.h" #include "spark.h" #include "vstdlib/random.h" #include "engine/IEngineSound.h" #include "tier1/strtools.h" #include "buttons.h" #include "eventqueue.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" void PlayLockSounds( CBaseEntity *pEdict, locksound_t *pls, int flocked, int fbutton ); string_t MakeButtonSound( int sound ); // get string of button sound number #define SF_BUTTON_DONTMOVE 1 #define SF_ROTBUTTON_NOTSOLID 1 #define SF_BUTTON_TOGGLE 32 // button stays pushed until reactivated #define SF_BUTTON_TOUCH_ACTIVATES 256 // Button fires when touched. #define SF_BUTTON_DAMAGE_ACTIVATES 512 // Button fires when damaged. #define SF_BUTTON_USE_ACTIVATES 1024 // Button fires when used. #define SF_BUTTON_LOCKED 2048 // Whether the button is initially locked. #define SF_BUTTON_SPARK_IF_OFF 4096 // button sparks in OFF state #define SF_BUTTON_JIGGLE_ON_USE_LOCKED 8192 // whether to jiggle if someone uses us when we're locked BEGIN_DATADESC( CBaseButton ) DEFINE_KEYFIELD( m_vecMoveDir, FIELD_VECTOR, "movedir" ), DEFINE_FIELD( m_fStayPushed, FIELD_BOOLEAN ), DEFINE_FIELD( m_fRotating, FIELD_BOOLEAN ), DEFINE_FIELD( m_bLockedSound, FIELD_CHARACTER ), DEFINE_FIELD( m_bLockedSentence, FIELD_CHARACTER ), DEFINE_FIELD( m_bUnlockedSound, FIELD_CHARACTER ), DEFINE_FIELD( m_bUnlockedSentence, FIELD_CHARACTER ), DEFINE_FIELD( m_bLocked, FIELD_BOOLEAN ), DEFINE_FIELD( m_sNoise, FIELD_SOUNDNAME ), DEFINE_FIELD( m_flUseLockedTime, FIELD_TIME ), DEFINE_FIELD( m_bSolidBsp, FIELD_BOOLEAN ), DEFINE_KEYFIELD( m_sounds, FIELD_INTEGER, "sounds" ), // DEFINE_FIELD( m_ls, FIELD_SOUNDNAME ), // This is restored in Precache() // DEFINE_FIELD( m_nState, FIELD_INTEGER ), // Function Pointers DEFINE_FUNCTION( ButtonTouch ), DEFINE_FUNCTION( ButtonSpark ), DEFINE_FUNCTION( TriggerAndWait ), DEFINE_FUNCTION( ButtonReturn ), DEFINE_FUNCTION( ButtonBackHome ), DEFINE_FUNCTION( ButtonUse ), // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "Lock", InputLock ), DEFINE_INPUTFUNC( FIELD_VOID, "Unlock", InputUnlock ), DEFINE_INPUTFUNC( FIELD_VOID, "Press", InputPress ), DEFINE_INPUTFUNC( FIELD_VOID, "PressIn", InputPressIn ), DEFINE_INPUTFUNC( FIELD_VOID, "PressOut", InputPressOut ), // Outputs DEFINE_OUTPUT( m_OnDamaged, "OnDamaged" ), DEFINE_OUTPUT( m_OnPressed, "OnPressed" ), DEFINE_OUTPUT( m_OnUseLocked, "OnUseLocked" ), DEFINE_OUTPUT( m_OnIn, "OnIn" ), DEFINE_OUTPUT( m_OnOut, "OnOut" ), END_DATADESC() LINK_ENTITY_TO_CLASS( func_button, CBaseButton ); IMPLEMENT_SERVERCLASS_ST( CBaseButton, DT_BaseButton ) END_SEND_TABLE() void CBaseButton::Precache( void ) { // get door button sounds, for doors which require buttons to open if (m_bLockedSound) { m_ls.sLockedSound = MakeButtonSound( (int)m_bLockedSound ); PrecacheScriptSound(m_ls.sLockedSound.ToCStr()); } if (m_bUnlockedSound) { m_ls.sUnlockedSound = MakeButtonSound( (int)m_bUnlockedSound ); PrecacheScriptSound(m_ls.sUnlockedSound.ToCStr()); } // get sentence group names, for doors which are directly 'touched' to open switch (m_bLockedSentence) { case 1: m_ls.sLockedSentence = MAKE_STRING("NA"); break; // access denied case 2: m_ls.sLockedSentence = MAKE_STRING("ND"); break; // security lockout case 3: m_ls.sLockedSentence = MAKE_STRING("NF"); break; // blast door case 4: m_ls.sLockedSentence = MAKE_STRING("NFIRE"); break; // fire door case 5: m_ls.sLockedSentence = MAKE_STRING("NCHEM"); break; // chemical door case 6: m_ls.sLockedSentence = MAKE_STRING("NRAD"); break; // radiation door case 7: m_ls.sLockedSentence = MAKE_STRING("NCON"); break; // gen containment case 8: m_ls.sLockedSentence = MAKE_STRING("NH"); break; // maintenance door case 9: m_ls.sLockedSentence = MAKE_STRING("NG"); break; // broken door default: m_ls.sLockedSentence = NULL_STRING; break; } switch (m_bUnlockedSentence) { case 1: m_ls.sUnlockedSentence = MAKE_STRING("EA"); break; // access granted case 2: m_ls.sUnlockedSentence = MAKE_STRING("ED"); break; // security door case 3: m_ls.sUnlockedSentence = MAKE_STRING("EF"); break; // blast door case 4: m_ls.sUnlockedSentence = MAKE_STRING("EFIRE"); break; // fire door case 5: m_ls.sUnlockedSentence = MAKE_STRING("ECHEM"); break; // chemical door case 6: m_ls.sUnlockedSentence = MAKE_STRING("ERAD"); break; // radiation door case 7: m_ls.sUnlockedSentence = MAKE_STRING("ECON"); break; // gen containment case 8: m_ls.sUnlockedSentence = MAKE_STRING("EH"); break; // maintenance door default: m_ls.sUnlockedSentence = NULL_STRING; break; } if ( m_sNoise != NULL_STRING ) { PrecacheScriptSound( STRING( m_sNoise ) ); } } //----------------------------------------------------------------------------- // Purpose: Cache user-entity-field values until spawn is called. // Input : szKeyName - // szValue - // Output : Returns true if handled, false if not. //----------------------------------------------------------------------------- bool CBaseButton::KeyValue( const char *szKeyName, const char *szValue ) { if (FStrEq(szKeyName, "locked_sound")) { m_bLockedSound = atof(szValue); } else if (FStrEq(szKeyName, "locked_sentence")) { m_bLockedSentence = atof(szValue); } else if (FStrEq(szKeyName, "unlocked_sound")) { m_bUnlockedSound = atof(szValue); } else if (FStrEq(szKeyName, "unlocked_sentence")) { m_bUnlockedSentence = atof(szValue); } else { return BaseClass::KeyValue( szKeyName, szValue ); } return true; } //----------------------------------------------------------------------------- // Purpose: Locks the button. If locked, the button will play the locked sound // when the player tries to use it. //----------------------------------------------------------------------------- void CBaseButton::Lock() { m_bLocked = true; } //----------------------------------------------------------------------------- // Purpose: Unlocks the button, making it able to be pressed again. //----------------------------------------------------------------------------- void CBaseButton::Unlock() { m_bLocked = false; } //----------------------------------------------------------------------------- // Purpose: Locks the button. If locked, the button will play the locked sound // when the player tries to use it. //----------------------------------------------------------------------------- void CBaseButton::InputLock( inputdata_t &inputdata ) { Lock(); } //----------------------------------------------------------------------------- // Purpose: Unlocks the button, making it able to be pressed again. //----------------------------------------------------------------------------- void CBaseButton::InputUnlock( inputdata_t &inputdata ) { Unlock(); } //----------------------------------------------------------------------------- // Presses or unpresses the button. //----------------------------------------------------------------------------- void CBaseButton::Press( CBaseEntity *pActivator, BUTTON_CODE eCode ) { if ( ( eCode == BUTTON_PRESS ) && ( m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) ) { return; } if ( ( eCode == BUTTON_ACTIVATE ) && ( m_toggle_state == TS_GOING_UP || m_toggle_state == TS_AT_TOP ) ) { return; } if ( ( eCode == BUTTON_RETURN ) && ( m_toggle_state == TS_GOING_DOWN || m_toggle_state == TS_AT_BOTTOM ) ) { return; } // FIXME: consolidate all the button press code into one place! if (m_bLocked) { // play button locked sound PlayLockSounds(this, &m_ls, TRUE, TRUE); return; } // Temporarily disable the touch function, until movement is finished. SetTouch( NULL ); if ( ( eCode == BUTTON_PRESS ) && ( m_toggle_state == TS_AT_TOP ) || ( ( eCode == BUTTON_RETURN ) && ( m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP ) ) ) { if ( m_sNoise != NULL_STRING ) { CPASAttenuationFilter filter( this ); EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = (char*)STRING(m_sNoise); ep.m_flVolume = 1; ep.m_SoundLevel = SNDLVL_NORM; EmitSound( filter, entindex(), ep ); } m_OnPressed.FireOutput(pActivator, this); ButtonReturn(); } else if ( ( eCode == BUTTON_PRESS ) || ( ( eCode == BUTTON_ACTIVATE ) && ( m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN ) ) ) { m_OnPressed.FireOutput(pActivator, this); ButtonActivate(); } } //----------------------------------------------------------------------------- // Presses the button. //----------------------------------------------------------------------------- void CBaseButton::InputPress( inputdata_t &inputdata ) { Press( inputdata.pActivator, BUTTON_PRESS ); } //----------------------------------------------------------------------------- // Presses the button, sending it to the top/pressed position. //----------------------------------------------------------------------------- void CBaseButton::InputPressIn( inputdata_t &inputdata ) { Press( inputdata.pActivator, BUTTON_ACTIVATE ); } //----------------------------------------------------------------------------- // Unpresses the button, sending it to the unpressed/bottom position. //----------------------------------------------------------------------------- void CBaseButton::InputPressOut( inputdata_t &inputdata ) { Press( inputdata.pActivator, BUTTON_RETURN ); } //----------------------------------------------------------------------------- // Purpose: We have been damaged. Possibly activate, depending on our flags. // Input : pInflictor - // pAttacker - // flDamage - // bitsDamageType - // Output : //----------------------------------------------------------------------------- int CBaseButton::OnTakeDamage( const CTakeDamageInfo &info ) { m_OnDamaged.FireOutput(m_hActivator, this); // dvsents2: remove obselete health keyvalue from func_button if (!HasSpawnFlags(SF_BUTTON_DAMAGE_ACTIVATES) && (m_iHealth == 0)) { return(0); } BUTTON_CODE code = ButtonResponseToTouch(); if ( code == BUTTON_NOTHING ) return 0; m_hActivator = info.GetAttacker(); // dvsents2: why would activator be NULL here? if ( m_hActivator == NULL ) return 0; if (m_bLocked) { return(0); } // Temporarily disable the touch function, until movement is finished. SetTouch( NULL ); if ( code == BUTTON_RETURN ) { if ( m_sNoise != NULL_STRING ) { CPASAttenuationFilter filter( this ); EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = (char*)STRING(m_sNoise); ep.m_flVolume = 1; ep.m_SoundLevel = SNDLVL_NORM; EmitSound( filter, entindex(), ep ); } m_OnPressed.FireOutput(m_hActivator, this); ButtonReturn(); } else { // code == BUTTON_ACTIVATE m_OnPressed.FireOutput(m_hActivator, this); ButtonActivate( ); } return 0; } void CBaseButton::Spawn( ) { //---------------------------------------------------- //determine sounds for buttons //a sound of 0 should not make a sound //---------------------------------------------------- if ( m_sounds ) { m_sNoise = MakeButtonSound( m_sounds ); PrecacheScriptSound(m_sNoise.ToCStr()); } else { m_sNoise = NULL_STRING; } Precache(); if ( HasSpawnFlags( SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state { SetThink ( &CBaseButton::ButtonSpark ); SetNextThink( gpGlobals->curtime + 0.5f );// no hurry, make sure everything else spawns } // Convert movedir from angles to a vector QAngle angMoveDir = QAngle( m_vecMoveDir.x, m_vecMoveDir.y, m_vecMoveDir.z ); AngleVectors( angMoveDir, &m_vecMoveDir ); SetMoveType( MOVETYPE_PUSH ); SetSolid( SOLID_BSP ); SetModel( STRING( GetModelName() ) ); if (m_flSpeed == 0) { m_flSpeed = 40; } m_takedamage = DAMAGE_YES; if (m_flWait == 0) { m_flWait = 1; } if (m_flLip == 0) { m_flLip = 4; } m_toggle_state = TS_AT_BOTTOM; m_vecPosition1 = GetLocalOrigin(); // Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big Vector vecButtonOBB = CollisionProp()->OBBSize(); vecButtonOBB -= Vector( 2, 2, 2 ); m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * (DotProductAbs( m_vecMoveDir, vecButtonOBB ) - m_flLip)); // Is this a non-moving button? if ( ((m_vecPosition2 - m_vecPosition1).Length() < 1) || HasSpawnFlags(SF_BUTTON_DONTMOVE) ) { m_vecPosition2 = m_vecPosition1; } m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); m_fRotating = FALSE; if (HasSpawnFlags(SF_BUTTON_LOCKED)) { m_bLocked = true; } // // If using activates the button, set its use function. // if (HasSpawnFlags(SF_BUTTON_USE_ACTIVATES)) { SetUse(&CBaseButton::ButtonUse); } else { SetUse(NULL); } // // If touching activates the button, set its touch function. // if (HasSpawnFlags(SF_BUTTON_TOUCH_ACTIVATES)) { SetTouch( &CBaseButton::ButtonTouch ); } else { SetTouch ( NULL ); } CreateVPhysics(); } //----------------------------------------------------------------------------- bool CBaseButton::CreateVPhysics() { VPhysicsInitShadow( false, false ); return true; } //----------------------------------------------------------------------------- // Purpose: Button sound table. // Also used by CBaseDoor to get 'touched' door lock/unlock sounds // Input : sound - index of sound to look up. // Output : Returns a pointer to the corresponding sound file. //----------------------------------------------------------------------------- string_t MakeButtonSound( int sound ) { char tmp[1024]; Q_snprintf( tmp, sizeof(tmp), "Buttons.snd%d", sound ); return AllocPooledString(tmp); } //----------------------------------------------------------------------------- // Purpose: Think function that emits sparks at random intervals. //----------------------------------------------------------------------------- void CBaseButton::ButtonSpark ( void ) { SetThink ( &CBaseButton::ButtonSpark ); SetNextThink( gpGlobals->curtime + 0.1 + random->RandomFloat ( 0, 1.5 ) );// spark again at random interval DoSpark( this, WorldSpaceCenter(), 1, 1, true, vec3_origin ); } //----------------------------------------------------------------------------- // Purpose: Called when someone uses us whilst we are locked. //----------------------------------------------------------------------------- bool CBaseButton::OnUseLocked( CBaseEntity *pActivator ) { PlayLockSounds(this, &m_ls, TRUE, TRUE); if ( gpGlobals->curtime > m_flUseLockedTime ) { m_OnUseLocked.FireOutput( pActivator, this ); m_flUseLockedTime = gpGlobals->curtime + 0.5; return true; } return false; } //----------------------------------------------------------------------------- // Purpose: Use function that starts the button moving. // Input : pActivator - // pCaller - // useType - // value - //----------------------------------------------------------------------------- void CBaseButton::ButtonUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. // UNDONE: Should this use ButtonResponseToTouch() too? if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN ) return; if (m_bLocked) { OnUseLocked( pActivator ); return; } m_hActivator = pActivator; if ( m_toggle_state == TS_AT_TOP) { // // If it's a toggle button it can return now. Otherwise, it will either // return on its own or will stay pressed indefinitely. // if ( HasSpawnFlags(SF_BUTTON_TOGGLE)) { if ( m_sNoise != NULL_STRING ) { CPASAttenuationFilter filter( this ); EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = (char*)STRING(m_sNoise); ep.m_flVolume = 1; ep.m_SoundLevel = SNDLVL_NORM; EmitSound( filter, entindex(), ep ); } m_OnPressed.FireOutput(m_hActivator, this); ButtonReturn(); } } else { m_OnPressed.FireOutput(m_hActivator, this); ButtonActivate( ); } } //----------------------------------------------------------------------------- // Purpose: Returns a code indicating how the button should respond to being touched. // Output : Returns one of the following: // BUTTON_NOTHING - do nothing // BUTTON_RETURN - // BUTTON_ACTIVATE - act as if pressed //----------------------------------------------------------------------------- CBaseButton::BUTTON_CODE CBaseButton::ButtonResponseToTouch( void ) { // Ignore touches if button is moving, or pushed-in and waiting to auto-come-out. if (m_toggle_state == TS_GOING_UP || m_toggle_state == TS_GOING_DOWN || (m_toggle_state == TS_AT_TOP && !m_fStayPushed && !HasSpawnFlags(SF_BUTTON_TOGGLE) ) ) return BUTTON_NOTHING; if (m_toggle_state == TS_AT_TOP) { if ( HasSpawnFlags(SF_BUTTON_TOGGLE) && !m_fStayPushed) { return BUTTON_RETURN; } } else return BUTTON_ACTIVATE; return BUTTON_NOTHING; } //----------------------------------------------------------------------------- // Purpose: Touch function that activates the button if it responds to touch. // Input : pOther - The entity that touched us. //----------------------------------------------------------------------------- void CBaseButton::ButtonTouch( CBaseEntity *pOther ) { // Ignore touches by anything but players if ( !pOther->IsPlayer() ) return; m_hActivator = pOther; BUTTON_CODE code = ButtonResponseToTouch(); if ( code == BUTTON_NOTHING ) return; if (!UTIL_IsMasterTriggered(m_sMaster, pOther) || m_bLocked) { // play button locked sound PlayLockSounds(this, &m_ls, TRUE, TRUE); return; } // Temporarily disable the touch function, until movement is finished. SetTouch( NULL ); if ( code == BUTTON_RETURN ) { if ( m_sNoise != NULL_STRING ) { CPASAttenuationFilter filter( this ); EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = (char*)STRING(m_sNoise); ep.m_flVolume = 1; ep.m_SoundLevel = SNDLVL_NORM; EmitSound( filter, entindex(), ep ); } m_OnPressed.FireOutput(m_hActivator, this); ButtonReturn(); } else { // code == BUTTON_ACTIVATE m_OnPressed.FireOutput(m_hActivator, this); ButtonActivate( ); } } //----------------------------------------------------------------------------- // Purpose: Starts the button moving "in/up". // Input : *pOther - //----------------------------------------------------------------------------- void CBaseButton::ButtonActivate( void ) { if ( m_sNoise != NULL_STRING ) { CPASAttenuationFilter filter( this ); EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = (char*)STRING(m_sNoise); ep.m_flVolume = 1; ep.m_SoundLevel = SNDLVL_NORM; EmitSound( filter, entindex(), ep ); } if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator) || m_bLocked) { // button is locked, play locked sound PlayLockSounds(this, &m_ls, TRUE, TRUE); return; } else { // button is unlocked, play unlocked sound PlayLockSounds(this, &m_ls, FALSE, TRUE); } ASSERT(m_toggle_state == TS_AT_BOTTOM); m_toggle_state = TS_GOING_UP; SetMoveDone( &CBaseButton::TriggerAndWait ); if (!m_fRotating) LinearMove( m_vecPosition2, m_flSpeed); else AngularMove( m_vecAngle2, m_flSpeed); } //----------------------------------------------------------------------------- // Purpose: Enables or disables the use capability based on our spawnflags. //----------------------------------------------------------------------------- int CBaseButton::ObjectCaps(void) { return((BaseClass::ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | (HasSpawnFlags(SF_BUTTON_USE_ACTIVATES) ? (FCAP_IMPULSE_USE | FCAP_USE_IN_RADIUS) : 0)); } //----------------------------------------------------------------------------- // Purpose: Button has reached the "pressed/top" position. Fire its OnIn output, // and pause before returning to "unpressed/bottom". //----------------------------------------------------------------------------- void CBaseButton::TriggerAndWait( void ) { ASSERT(m_toggle_state == TS_GOING_UP); if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator) || m_bLocked) { return; } m_toggle_state = TS_AT_TOP; // // Re-instate touches if the button is of the toggle variety. // if (m_fStayPushed || HasSpawnFlags(SF_BUTTON_TOGGLE ) ) { if (HasSpawnFlags(SF_BUTTON_TOUCH_ACTIVATES)) { SetTouch(&CBaseButton::ButtonTouch); } else { // BUGBUG: ALL buttons no longer respond to touch SetTouch (NULL); } } // // If button automatically comes back out, start it moving out. // else { SetNextThink( gpGlobals->curtime + m_flWait ); SetThink( &CBaseButton::ButtonReturn ); } m_nState = 1; // use alternate textures m_OnIn.FireOutput(m_hActivator, this); } //----------------------------------------------------------------------------- // Purpose: Starts the button moving "out/down". //----------------------------------------------------------------------------- void CBaseButton::ButtonReturn( void ) { ASSERT(m_toggle_state == TS_AT_TOP); m_toggle_state = TS_GOING_DOWN; SetMoveDone( &CBaseButton::ButtonBackHome ); if (!m_fRotating) LinearMove( m_vecPosition1, m_flSpeed); else AngularMove( m_vecAngle1, m_flSpeed); m_nState = 0; // use normal textures } //----------------------------------------------------------------------------- // Purpose: Button has returned to the "unpressed/bottom" position. Fire its // OnOut output and stop moving. //----------------------------------------------------------------------------- void CBaseButton::ButtonBackHome( void ) { ASSERT(m_toggle_state == TS_GOING_DOWN); m_toggle_state = TS_AT_BOTTOM; m_OnOut.FireOutput(m_hActivator, this); // // Re-instate touch method, movement cycle is complete. // if (HasSpawnFlags(SF_BUTTON_TOUCH_ACTIVATES)) { SetTouch( &CBaseButton::ButtonTouch ); } else { // BUGBUG: ALL buttons no longer respond to touch SetTouch ( NULL ); } // reset think for a sparking button if (HasSpawnFlags( SF_BUTTON_SPARK_IF_OFF ) ) { SetThink ( &CBaseButton::ButtonSpark ); SetNextThink( gpGlobals->curtime + 0.5f );// no hurry } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- int CBaseButton::DrawDebugTextOverlays() { int text_offset = BaseClass::DrawDebugTextOverlays(); if (m_debugOverlays & OVERLAY_TEXT_BIT) { static char *pszStates[] = { "Pressed", "Unpressed", "Pressing...", "Unpressing...", "", }; char tempstr[255]; int nState = m_toggle_state; if ( ( nState < 0 ) || ( nState > 3 ) ) { nState = 4; } Q_snprintf( tempstr, sizeof(tempstr), "State: %s", pszStates[nState] ); EntityText( text_offset, tempstr, 0 ); text_offset++; Q_snprintf( tempstr, sizeof(tempstr), "%s", m_bLocked ? "Locked" : "Unlocked" ); EntityText( text_offset, tempstr, 0 ); text_offset++; } return text_offset; } // // Rotating button (aka "lever") // LINK_ENTITY_TO_CLASS( func_rot_button, CRotButton ); void CRotButton::Spawn( void ) { //---------------------------------------------------- //determine sounds for buttons //a sound of 0 should not make a sound //---------------------------------------------------- if ( m_sounds ) { m_sNoise = MakeButtonSound( m_sounds ); PrecacheScriptSound(m_sNoise.ToCStr()); } else { m_sNoise = NULL_STRING; } // set the axis of rotation CBaseToggle::AxisDir(); // check for clockwise rotation if ( HasSpawnFlags( SF_DOOR_ROTATE_BACKWARDS) ) { m_vecMoveAng = m_vecMoveAng * -1; } SetMoveType( MOVETYPE_PUSH ); SetSolid( SOLID_VPHYSICS ); if ( HasSpawnFlags( SF_ROTBUTTON_NOTSOLID ) ) { AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); AddSolidFlags( FSOLID_NOT_SOLID ); } SetModel( STRING( GetModelName() ) ); if (m_flSpeed == 0) m_flSpeed = 40; if (m_flWait == 0) m_flWait = 1; if (m_iHealth > 0) { m_takedamage = DAMAGE_YES; } m_toggle_state = TS_AT_BOTTOM; m_vecAngle1 = GetLocalAngles(); m_vecAngle2 = GetLocalAngles() + m_vecMoveAng * m_flMoveDistance; ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating button start/end positions are equal\n"); m_fStayPushed = (m_flWait == -1 ? TRUE : FALSE); m_fRotating = TRUE; SetUse(&CRotButton::ButtonUse); // // If touching activates the button, set its touch function. // if (!HasSpawnFlags(SF_BUTTON_TOUCH_ACTIVATES)) { SetTouch ( NULL ); } else { SetTouch( &CRotButton::ButtonTouch ); } CreateVPhysics(); } bool CRotButton::CreateVPhysics( void ) { VPhysicsInitShadow( false, false ); return true; } //----------------------------------------------------------------------------- // CMomentaryRotButton spawnflags //----------------------------------------------------------------------------- #define SF_MOMENTARY_DOOR 1 #define SF_MOMENTARY_NOT_USABLE 2 #define SF_MOMENTARY_AUTO_RETURN 16 BEGIN_DATADESC( CMomentaryRotButton ) DEFINE_FIELD( m_lastUsed, FIELD_INTEGER ), DEFINE_FIELD( m_start, FIELD_VECTOR ), DEFINE_FIELD( m_end, FIELD_VECTOR ), DEFINE_FIELD( m_IdealYaw, FIELD_FLOAT ), DEFINE_FIELD( m_sNoise, FIELD_SOUNDNAME ), DEFINE_FIELD( m_bUpdateTarget, FIELD_BOOLEAN ), DEFINE_KEYFIELD( m_direction, FIELD_INTEGER, "StartDirection" ), DEFINE_KEYFIELD( m_returnSpeed, FIELD_FLOAT, "returnspeed" ), DEFINE_KEYFIELD( m_flStartPosition, FIELD_FLOAT, "StartPosition"), DEFINE_KEYFIELD( m_bSolidBsp, FIELD_BOOLEAN, "solidbsp" ), // Function Pointers DEFINE_FUNCTION( UseMoveDone ), DEFINE_FUNCTION( ReturnMoveDone ), DEFINE_FUNCTION( SetPositionMoveDone ), DEFINE_FUNCTION( UpdateThink ), // Inputs DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPosition", InputSetPosition ), DEFINE_INPUTFUNC( FIELD_FLOAT, "SetPositionImmediately", InputSetPositionImmediately ), DEFINE_INPUTFUNC( FIELD_VOID, "_DisableUpdateTarget", InputDisableUpdateTarget ), DEFINE_INPUTFUNC( FIELD_VOID, "_EnableUpdateTarget", InputEnableUpdateTarget ), // Outputs DEFINE_OUTPUT( m_Position, "Position" ), DEFINE_OUTPUT( m_OnUnpressed, "OnUnpressed" ), DEFINE_OUTPUT( m_OnFullyClosed, "OnFullyClosed" ), DEFINE_OUTPUT( m_OnFullyOpen, "OnFullyOpen" ), DEFINE_OUTPUT( m_OnReachedPosition, "OnReachedPosition" ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), DEFINE_FIELD( m_bDisabled, FIELD_BOOLEAN ) END_DATADESC() LINK_ENTITY_TO_CLASS( momentary_rot_button, CMomentaryRotButton ); //----------------------------------------------------------------------------- // Purpose: Called when spawning, after keyvalues have been handled. //----------------------------------------------------------------------------- void CMomentaryRotButton::Spawn( void ) { CBaseToggle::AxisDir(); m_bUpdateTarget = true; if ( m_flSpeed == 0 ) { m_flSpeed = 100; } // Clamp start position and issue bounds warning if (m_flStartPosition < 0.0 || m_flStartPosition > 1.0) { Warning("WARNING: Momentary door (%s) start position not between 0 and 1. Clamping.\n",GetDebugName()); m_flStartPosition = clamp(m_IdealYaw, 0, 1); } // Check direction fields (for backward compatibility) if (m_direction != 1 && m_direction != -1) { m_direction = 1; } if (m_flMoveDistance < 0) { m_vecMoveAng = m_vecMoveAng * -1; m_flMoveDistance = -m_flMoveDistance; } m_start = GetLocalAngles() - m_vecMoveAng * m_flMoveDistance * m_flStartPosition; m_end = GetLocalAngles() + m_vecMoveAng * m_flMoveDistance * (1-m_flStartPosition); m_IdealYaw = m_flStartPosition; // Force start direction at end points if (m_flStartPosition == 0.0) { m_direction = -1; } else if (m_flStartPosition == 1.0) { m_direction = 1; } if (HasSpawnFlags(SF_BUTTON_LOCKED)) { m_bLocked = true; } if ( HasSpawnFlags( SF_BUTTON_USE_ACTIVATES ) ) { if ( m_sounds ) { m_sNoise = MakeButtonSound( m_sounds ); PrecacheScriptSound(m_sNoise.ToCStr()); } else { m_sNoise = NULL_STRING; } m_lastUsed = 0; UpdateTarget(0,this); } SetSolid( SOLID_VPHYSICS ); if (HasSpawnFlags(SF_ROTBUTTON_NOTSOLID)) { AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID ); AddSolidFlags( FSOLID_NOT_SOLID ); } SetMoveType( MOVETYPE_PUSH ); SetModel( STRING( GetModelName() ) ); CreateVPhysics(); // Slam the object back to solid - if we really want it to be solid. if ( m_bSolidBsp ) { SetSolid( SOLID_BSP ); } m_bDisabled = false; } int CMomentaryRotButton::ObjectCaps( void ) { int flags = BaseClass::ObjectCaps(); if (!HasSpawnFlags(SF_BUTTON_USE_ACTIVATES)) { return flags; } else { return (flags | FCAP_CONTINUOUS_USE | FCAP_USE_IN_RADIUS); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CMomentaryRotButton::CreateVPhysics( void ) { VPhysicsInitShadow( false, false ); return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMomentaryRotButton::PlaySound( void ) { if ( m_sNoise == NULL_STRING ) return; CPASAttenuationFilter filter( this ); EmitSound_t ep; ep.m_nChannel = CHAN_VOICE; ep.m_pSoundName = (char*)STRING(m_sNoise); ep.m_flVolume = 1; ep.m_SoundLevel = SNDLVL_NORM; EmitSound( filter, entindex(), ep ); } //----------------------------------------------------------------------------- // Purpose: Returns a given angular position as a value along our motion from 0 to 1. // Input : vecAngles - //----------------------------------------------------------------------------- float CMomentaryRotButton::GetPos( const QAngle &vecAngles ) { float flScale = 1; if (( m_vecMoveAng[0] < 0 ) || ( m_vecMoveAng[1] < 0 ) || ( m_vecMoveAng[2] < 0 )) { flScale = -1; } float flPos = flScale * CBaseToggle::AxisDelta( m_spawnflags, vecAngles, m_start ) / m_flMoveDistance; return( clamp( flPos, 0, 1 )); } //------------------------------------------------------------------------------ // Purpose : // Input : flPosition //------------------------------------------------------------------------------ void CMomentaryRotButton::InputSetPosition( inputdata_t &inputdata ) { m_IdealYaw = clamp( inputdata.value.Float(), 0, 1 ); float flCurPos = GetPos( GetLocalAngles() ); if ( flCurPos < m_IdealYaw ) { // Moving forward (from start to end). SetLocalAngularVelocity( m_flSpeed * m_vecMoveAng ); m_direction = 1; } else if ( flCurPos > m_IdealYaw ) { // Moving backward (from end to start). SetLocalAngularVelocity( -m_flSpeed * m_vecMoveAng ); m_direction = -1; } else { // We're there already; nothing to do. SetLocalAngularVelocity( vec3_angle ); return; } SetMoveDone( &CMomentaryRotButton::SetPositionMoveDone ); SetThink( &CMomentaryRotButton::UpdateThink ); SetNextThink( gpGlobals->curtime ); // // Think again in 0.1 seconds or the time that it will take us to reach our movement goal, // whichever is the shorter interval. This prevents us from overshooting and stuttering when we // are told to change position in very small increments. // QAngle vecNewAngles = m_start + m_vecMoveAng * ( m_IdealYaw * m_flMoveDistance ); float flAngleDelta = fabs( AxisDelta( m_spawnflags, vecNewAngles, GetLocalAngles() )); float dt = flAngleDelta / m_flSpeed; if ( dt < TICK_INTERVAL ) { dt = TICK_INTERVAL; float speed = flAngleDelta / TICK_INTERVAL; SetLocalAngularVelocity( speed * m_vecMoveAng * m_direction ); } dt = clamp( dt, TICK_INTERVAL, TICK_INTERVAL * 6); SetMoveDoneTime( dt ); } //------------------------------------------------------------------------------ // Purpose : // Input : flPosition //------------------------------------------------------------------------------ void CMomentaryRotButton::InputSetPositionImmediately( inputdata_t &inputdata ) { m_IdealYaw = clamp( inputdata.value.Float(), 0, 1 ); SetLocalAngles( m_start + m_vecMoveAng * ( m_IdealYaw * m_flMoveDistance ) ); } //------------------------------------------------------------------------------ // Purpose: Turns off target updates so that we can change the wheel's position // without changing the target's position. Used for jiggling when locked. //------------------------------------------------------------------------------ void CMomentaryRotButton::InputDisableUpdateTarget( inputdata_t &inputdata ) { m_bUpdateTarget = false; } //------------------------------------------------------------------------------ // Purpose: Turns target updates back on (after jiggling). //------------------------------------------------------------------------------ void CMomentaryRotButton::InputEnableUpdateTarget( inputdata_t &inputdata ) { m_bUpdateTarget = true; } //----------------------------------------------------------------------------- // Purpose: Locks the button. If locked, the button will play the locked sound // when the player tries to use it. //----------------------------------------------------------------------------- void CMomentaryRotButton::Lock() { BaseClass::Lock(); SetLocalAngularVelocity( vec3_angle ); SetMoveDoneTime( -1 ); SetMoveDone( NULL ); SetNextThink( TICK_NEVER_THINK ); SetThink( NULL ); } //----------------------------------------------------------------------------- // Purpose: Unlocks the button, making it able to be pressed again. //----------------------------------------------------------------------------- void CMomentaryRotButton::Unlock() { BaseClass::Unlock(); SetMoveDone( &CMomentaryRotButton::ReturnMoveDone ); // Delay before autoreturn. SetMoveDoneTime( 0.1f ); } //----------------------------------------------------------------------------- // Purpose: Fires the appropriate outputs at the extremes of motion. //----------------------------------------------------------------------------- void CMomentaryRotButton::OutputMovementComplete( void ) { if (m_IdealYaw == 1.0) { m_OnFullyClosed.FireOutput(this, this); } else if (m_IdealYaw == 0.0) { m_OnFullyOpen.FireOutput(this, this); } m_OnReachedPosition.FireOutput( this, this ); } //------------------------------------------------------------------------------ // Purpose: MoveDone function for the SetPosition input handler. Tracks our // progress toward a movement goal and updates our outputs. //------------------------------------------------------------------------------ void CMomentaryRotButton::SetPositionMoveDone(void) { float flCurPos = GetPos( GetLocalAngles() ); if ((( flCurPos >= m_IdealYaw ) && ( m_direction == 1 )) || (( flCurPos <= m_IdealYaw ) && ( m_direction == -1 ))) { // // We reached or surpassed our movement goal. // SetLocalAngularVelocity( vec3_angle ); // BUGBUG: Won't this get the player stuck? SetLocalAngles( m_start + m_vecMoveAng * ( m_IdealYaw * m_flMoveDistance ) ); SetNextThink( TICK_NEVER_THINK ); SetMoveDoneTime( -1 ); UpdateTarget( m_IdealYaw, this ); OutputMovementComplete(); return; } // TODO: change this to use a Think function like ReturnThink. QAngle vecNewAngles = m_start + m_vecMoveAng * ( m_IdealYaw * m_flMoveDistance ); float flAngleDelta = fabs( AxisDelta( m_spawnflags, vecNewAngles, GetLocalAngles() )); float dt = flAngleDelta / m_flSpeed; if ( dt < TICK_INTERVAL ) { dt = TICK_INTERVAL; float speed = flAngleDelta / TICK_INTERVAL; SetLocalAngularVelocity( speed * m_vecMoveAng * m_direction ); } dt = clamp( dt, TICK_INTERVAL, TICK_INTERVAL * 6); SetMoveDoneTime( dt ); } //----------------------------------------------------------------------------- // Purpose: // Input : pActivator - // pCaller - // useType - // value - //----------------------------------------------------------------------------- void CMomentaryRotButton::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { if ( m_bDisabled == true ) return; if (m_bLocked) { if ( OnUseLocked( pActivator ) && HasSpawnFlags( SF_BUTTON_JIGGLE_ON_USE_LOCKED ) ) { // Jiggle two degrees. float flDist = 2.0 / m_flMoveDistance; // Must be first! g_EventQueue.AddEvent( this, "_DisableUpdateTarget", 0, this, this ); variant_t value; value.SetFloat( flDist ); g_EventQueue.AddEvent( this, "SetPosition", value, 0.01, this, this ); value.SetFloat( 0.0 ); g_EventQueue.AddEvent( this, "SetPosition", value, 0.1, this, this ); value.SetFloat( 0.5 * flDist ); g_EventQueue.AddEvent( this, "SetPosition", value, 0.2, this, this ); value.SetFloat( 0.0 ); g_EventQueue.AddEvent( this, "SetPosition", value, 0.3, this, this ); // Must be last! And must be late enough to cover the settling time. g_EventQueue.AddEvent( this, "_EnableUpdateTarget", 0.5, this, this ); } return; } // // Reverse our direction and play movement sound every time the player // pauses between uses. // bool bPlaySound = false; if ( !m_lastUsed ) { bPlaySound = true; m_direction = -m_direction; //Alert that we've been pressed m_OnPressed.FireOutput( m_hActivator, this ); } m_lastUsed = 1; float flPos = GetPos( GetLocalAngles() ); UpdateSelf( flPos, bPlaySound ); // // Think every frame while we are moving. // HACK: Don't reset the think time if we already have a pending think. // This works around an issue with host_thread_mode > 0 when the player's // clock runs ahead of the server. // if ( !m_pfnThink ) { SetThink( &CMomentaryRotButton::UpdateThink ); SetNextThink( gpGlobals->curtime ); } } //----------------------------------------------------------------------------- // Purpose: Handles changing direction at the extremes of our range of motion // and updating our avelocity while being used by the player. // Input : value - Number from 0 to 1 indicating our desired position within // our range of motion, 0 = start, 1 = end. //----------------------------------------------------------------------------- void CMomentaryRotButton::UpdateSelf( float value, bool bPlaySound ) { // // Set our move clock to 0.1 seconds in the future so we stop spinning unless we are // used again before then. // SetMoveDoneTime( 0.1 ); // // If we hit the end, zero our avelocity and snap to the end angles. // if ( m_direction > 0 && value >= 1.0 ) { SetLocalAngularVelocity( vec3_angle ); SetLocalAngles( m_end ); m_OnFullyClosed.FireOutput(this, this); return; } // // If we returned to the start, zero our avelocity and snap to the start angles. // else if ( m_direction < 0 && value <= 0 ) { SetLocalAngularVelocity( vec3_angle ); SetLocalAngles( m_start ); m_OnFullyOpen.FireOutput(this, this); return; } if ( bPlaySound ) { PlaySound(); } SetLocalAngularVelocity( ( m_direction * m_flSpeed ) * m_vecMoveAng ); SetMoveDone( &CMomentaryRotButton::UseMoveDone ); } //----------------------------------------------------------------------------- // Purpose: Updates the value of our position, firing any targets. // Input : value - New position, from 0 - 1. //----------------------------------------------------------------------------- void CMomentaryRotButton::UpdateTarget( float value, CBaseEntity *pActivator ) { if ( !m_bUpdateTarget ) return; if (m_Position.Get() != value) { m_Position.Set(value, pActivator, this); } } //----------------------------------------------------------------------------- // Purpose: Handles the end of motion caused by player use. //----------------------------------------------------------------------------- void CMomentaryRotButton::UseMoveDone( void ) { SetLocalAngularVelocity( vec3_angle ); // Make sure our targets stop where we stopped. float flPos = GetPos( GetLocalAngles() ); UpdateTarget( flPos, this ); // Alert that we've been unpressed m_OnUnpressed.FireOutput( m_hActivator, this ); m_lastUsed = 0; if ( !HasSpawnFlags( SF_BUTTON_TOGGLE ) && m_returnSpeed > 0 ) { SetMoveDone( &CMomentaryRotButton::ReturnMoveDone ); m_direction = -1; // Delay before autoreturn. SetMoveDoneTime( 0.1f ); } else { SetThink( NULL ); SetMoveDone( NULL ); } } //----------------------------------------------------------------------------- // Purpose: MoveDone function for rotating back to the start position. //----------------------------------------------------------------------------- void CMomentaryRotButton::ReturnMoveDone( void ) { float value = GetPos( GetLocalAngles() ); if ( value <= 0 ) { // // Got back to the start, stop spinning. // SetLocalAngularVelocity( vec3_angle ); SetLocalAngles( m_start ); UpdateTarget( 0, NULL ); SetMoveDoneTime( -1 ); SetMoveDone( NULL ); SetNextThink( TICK_NEVER_THINK ); SetThink( NULL ); } else { SetLocalAngularVelocity( -m_returnSpeed * m_vecMoveAng ); SetMoveDoneTime( 0.1f ); SetThink( &CMomentaryRotButton::UpdateThink ); SetNextThink( gpGlobals->curtime + 0.01f ); } } //----------------------------------------------------------------------------- // Purpose: Think function for updating target as we move. //----------------------------------------------------------------------------- void CMomentaryRotButton::UpdateThink( void ) { float value = GetPos( GetLocalAngles() ); UpdateTarget( value, NULL ); SetNextThink( gpGlobals->curtime ); } //----------------------------------------------------------------------------- // Purpose: Draw any debug text overlays // Output : Current text offset from the top //----------------------------------------------------------------------------- int CMomentaryRotButton::DrawDebugTextOverlays(void) { int text_offset = BaseClass::DrawDebugTextOverlays(); if (m_debugOverlays & OVERLAY_TEXT_BIT) { char tempstr[255]; Q_snprintf(tempstr,sizeof(tempstr),"QAngle: %.2f %.2f %.2f", GetLocalAngles()[0], GetLocalAngles()[1], GetLocalAngles()[2]); EntityText(text_offset,tempstr,0); text_offset++; Q_snprintf(tempstr,sizeof(tempstr),"AVelocity: %.2f %.2f %.2f", GetLocalAngularVelocity()[0], GetLocalAngularVelocity()[1], GetLocalAngularVelocity()[2]); EntityText(text_offset,tempstr,0); text_offset++; Q_snprintf(tempstr,sizeof(tempstr),"Target Pos: %3.3f",m_IdealYaw); EntityText(text_offset,tempstr,0); text_offset++; float flCurPos = GetPos(GetLocalAngles()); Q_snprintf(tempstr,sizeof(tempstr),"Current Pos: %3.3f",flCurPos); EntityText(text_offset,tempstr,0); text_offset++; Q_snprintf(tempstr,sizeof(tempstr),"Direction: %s",(m_direction == 1) ? "Forward" : "Backward"); EntityText(text_offset,tempstr,0); text_offset++; } return text_offset; } //----------------------------------------------------------------------------- // Purpose: Input hander that starts the spawner //----------------------------------------------------------------------------- void CMomentaryRotButton::InputEnable( inputdata_t &inputdata ) { Enable(); } //----------------------------------------------------------------------------- // Purpose: Input hander that stops the spawner //----------------------------------------------------------------------------- void CMomentaryRotButton::InputDisable( inputdata_t &inputdata ) { Disable(); } //----------------------------------------------------------------------------- // Purpose: Start the spawner //----------------------------------------------------------------------------- void CMomentaryRotButton::Enable( void ) { m_bDisabled = false; } //----------------------------------------------------------------------------- // Purpose: Stop the spawner //----------------------------------------------------------------------------- void CMomentaryRotButton::Disable( void ) { m_bDisabled = true; }