You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1765 lines
46 KiB
1765 lines
46 KiB
/*** |
|
* |
|
* Copyright (c) 1996-2002, Valve LLC. All rights reserved. |
|
* |
|
* This product contains software technology licensed from Id |
|
* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. |
|
* All Rights Reserved. |
|
* |
|
* Use, distribution, and modification of this source code and/or resulting |
|
* object code is restricted to non-commercial enhancements to products from |
|
* Valve LLC. All other use, distribution, or modification is prohibited |
|
* without written permission from Valve LLC. |
|
* |
|
****/ |
|
/* |
|
|
|
===== buttons.cpp ======================================================== |
|
|
|
button-related code |
|
|
|
*/ |
|
|
|
#include "extdll.h" |
|
#include "util.h" |
|
#include "cbase.h" |
|
#include "saverestore.h" |
|
#include "doors.h" |
|
|
|
#define SF_BUTTON_DONTMOVE 1 |
|
#define SF_ROTBUTTON_NOTSOLID 1 |
|
#define SF_BUTTON_ONLYDIRECT 16 //LRC - button can't be used through walls. |
|
#define SF_BUTTON_TOGGLE 32 // button stays pushed until reactivated |
|
#define SF_BUTTON_SPARK_IF_OFF 64 // button sparks in OFF state |
|
#define SF_BUTTON_NOT_SOLID 128 // button isn't solid |
|
#define SF_BUTTON_TOUCH_ONLY 256 // button must be touched to be used. |
|
#define SF_BUTTON_USEKEY 512 // change the reaction of the button to the USE key. |
|
// (i.e. if it's meant to be ignored, don't ignore it; otherwise ignore it.) |
|
|
|
#define SF_GLOBAL_SET 1 // Set global state to initial state on spawn |
|
|
|
class CEnvGlobal : public CPointEntity |
|
{ |
|
public: |
|
void Spawn( void ); |
|
void KeyValue( KeyValueData *pkvd ); |
|
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); |
|
|
|
virtual int Save( CSave &save ); |
|
virtual int Restore( CRestore &restore ); |
|
static TYPEDESCRIPTION m_SaveData[]; |
|
|
|
string_t m_globalstate; |
|
int m_triggermode; |
|
int m_initialstate; |
|
}; |
|
|
|
TYPEDESCRIPTION CEnvGlobal::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CEnvGlobal, m_globalstate, FIELD_STRING ), |
|
DEFINE_FIELD( CEnvGlobal, m_triggermode, FIELD_INTEGER ), |
|
DEFINE_FIELD( CEnvGlobal, m_initialstate, FIELD_INTEGER ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CEnvGlobal, CPointEntity ) |
|
|
|
LINK_ENTITY_TO_CLASS( env_global, CEnvGlobal ) |
|
|
|
void CEnvGlobal::KeyValue( KeyValueData *pkvd ) |
|
{ |
|
pkvd->fHandled = TRUE; |
|
|
|
if( FStrEq( pkvd->szKeyName, "globalstate" ) ) // State name |
|
m_globalstate = ALLOC_STRING( pkvd->szValue ); |
|
else if( FStrEq( pkvd->szKeyName, "triggermode" ) ) |
|
m_triggermode = atoi( pkvd->szValue ); |
|
else if( FStrEq( pkvd->szKeyName, "initialstate" ) ) |
|
m_initialstate = atoi( pkvd->szValue ); |
|
else |
|
CPointEntity::KeyValue( pkvd ); |
|
} |
|
|
|
void CEnvGlobal::Spawn( void ) |
|
{ |
|
if( !m_globalstate ) |
|
{ |
|
REMOVE_ENTITY( ENT( pev ) ); |
|
return; |
|
} |
|
if( FBitSet( pev->spawnflags, SF_GLOBAL_SET ) ) |
|
{ |
|
if( !gGlobalState.EntityInTable( m_globalstate ) ) |
|
gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, (GLOBALESTATE)m_initialstate ); |
|
} |
|
} |
|
|
|
void CEnvGlobal::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
GLOBALESTATE oldState = gGlobalState.EntityGetState( m_globalstate ); |
|
GLOBALESTATE newState; |
|
|
|
switch( m_triggermode ) |
|
{ |
|
case 0: |
|
newState = GLOBAL_OFF; |
|
break; |
|
case 1: |
|
newState = GLOBAL_ON; |
|
break; |
|
case 2: |
|
newState = GLOBAL_DEAD; |
|
break; |
|
default: |
|
case 3: |
|
if( oldState == GLOBAL_ON ) |
|
newState = GLOBAL_OFF; |
|
else if( oldState == GLOBAL_OFF ) |
|
newState = GLOBAL_ON; |
|
else |
|
newState = oldState; |
|
} |
|
|
|
if( gGlobalState.EntityInTable( m_globalstate ) ) |
|
gGlobalState.EntitySetState( m_globalstate, newState ); |
|
else |
|
gGlobalState.EntityAdd( m_globalstate, gpGlobals->mapname, newState ); |
|
} |
|
|
|
|
|
//================================================== |
|
//LRC- a simple entity, just maintains a state |
|
//================================================== |
|
|
|
#define SF_ENVSTATE_START_ON 1 |
|
#define SF_ENVSTATE_DEBUG 2 |
|
|
|
class CEnvState : public CPointEntity |
|
{ |
|
public: |
|
void Spawn( void ); |
|
void Think( void ); |
|
void KeyValue( KeyValueData *pkvd ); |
|
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); |
|
|
|
BOOL IsLockedByMaster( void ) { return !UTIL_IsMasterTriggered(m_sMaster, NULL); }; |
|
|
|
virtual int Save( CSave &save ); |
|
virtual int Restore( CRestore &restore ); |
|
|
|
virtual STATE GetState() { return m_iState; } |
|
|
|
static TYPEDESCRIPTION m_SaveData[]; |
|
|
|
STATE m_iState; |
|
float m_fTurnOnTime; |
|
float m_fTurnOffTime; |
|
int m_sMaster; |
|
}; |
|
|
|
void CEnvState::Spawn( void ) |
|
{ |
|
if (pev->spawnflags & SF_ENVSTATE_START_ON) |
|
m_iState = STATE_ON; |
|
else |
|
m_iState = STATE_OFF; |
|
} |
|
|
|
TYPEDESCRIPTION CEnvState::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CEnvState, m_iState, FIELD_INTEGER ), |
|
DEFINE_FIELD( CEnvState, m_fTurnOnTime, FIELD_INTEGER ), |
|
DEFINE_FIELD( CEnvState, m_fTurnOffTime, FIELD_INTEGER ), |
|
DEFINE_FIELD( CEnvState, m_sMaster, FIELD_STRING ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CEnvState, CPointEntity ); |
|
|
|
LINK_ENTITY_TO_CLASS( env_state, CEnvState ); |
|
|
|
void CEnvState::KeyValue( KeyValueData *pkvd ) |
|
{ |
|
pkvd->fHandled = TRUE; |
|
|
|
if ( FStrEq(pkvd->szKeyName, "turnontime") ) |
|
m_fTurnOnTime = atof( pkvd->szValue ); |
|
else if ( FStrEq(pkvd->szKeyName, "turnofftime") ) |
|
m_fTurnOffTime = atof( pkvd->szValue ); |
|
else if ( FStrEq(pkvd->szKeyName, "master") ) |
|
m_sMaster = ALLOC_STRING( pkvd->szValue ); |
|
else |
|
CPointEntity::KeyValue( pkvd ); |
|
} |
|
|
|
void CEnvState::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
if (!ShouldToggle(useType) || IsLockedByMaster()) |
|
{ |
|
if (pev->spawnflags & SF_ENVSTATE_DEBUG) |
|
{ |
|
ALERT(at_console,"DEBUG: env_state \"%s\" ",STRING(pev->targetname)); |
|
if (IsLockedByMaster()) |
|
ALERT(at_console,"ignored trigger %s; locked by master \"%s\".\n",GetStringForUseType(useType),STRING(m_sMaster)); |
|
else if (useType == USE_ON) |
|
ALERT(at_console,"ignored trigger USE_ON; already on\n"); |
|
else if (useType == USE_OFF) |
|
ALERT(at_console,"ignored trigger USE_OFF; already off\n"); |
|
else |
|
ALERT(at_console,"ignored trigger %s.\n",GetStringForUseType(useType)); |
|
} |
|
return; |
|
} |
|
|
|
switch (GetState()) |
|
{ |
|
case STATE_ON: |
|
case STATE_TURN_ON: |
|
if (m_fTurnOffTime) |
|
{ |
|
m_iState = STATE_TURN_OFF; |
|
if (pev->spawnflags & SF_ENVSTATE_DEBUG) |
|
{ |
|
ALERT(at_console,"DEBUG: env_state \"%s\" triggered; will turn off in %f seconds.\n", STRING(pev->targetname), (double)m_fTurnOffTime); |
|
} |
|
SetNextThink( m_fTurnOffTime ); |
|
} |
|
else |
|
{ |
|
m_iState = STATE_OFF; |
|
if (pev->spawnflags & SF_ENVSTATE_DEBUG) |
|
{ |
|
ALERT(at_console,"DEBUG: env_state \"%s\" triggered, turned off", STRING(pev->targetname)); |
|
if (pev->target) |
|
{ |
|
ALERT(at_console,": firing \"%s\"",STRING(pev->target)); |
|
if (pev->noise2) |
|
ALERT(at_console," and \"%s\"",STRING(pev->noise2)); |
|
} |
|
else if (pev->noise2) |
|
ALERT(at_console,": firing \"%s\"",STRING(pev->noise2)); |
|
ALERT(at_console,".\n"); |
|
} |
|
FireTargets(STRING(pev->target),pActivator,this,USE_OFF,0); |
|
FireTargets(STRING(pev->noise2),pActivator,this,USE_TOGGLE,0); |
|
DontThink(); |
|
} |
|
break; |
|
case STATE_OFF: |
|
case STATE_TURN_OFF: |
|
if (m_fTurnOnTime) |
|
{ |
|
m_iState = STATE_TURN_ON; |
|
if (pev->spawnflags & SF_ENVSTATE_DEBUG) |
|
{ |
|
ALERT(at_console,"DEBUG: env_state \"%s\" triggered; will turn on in %f seconds.\n", STRING(pev->targetname), (double)m_fTurnOnTime); |
|
} |
|
SetNextThink( m_fTurnOnTime ); |
|
} |
|
else |
|
{ |
|
m_iState = STATE_ON; |
|
if (pev->spawnflags & SF_ENVSTATE_DEBUG) |
|
{ |
|
ALERT(at_console,"DEBUG: env_state \"%s\" triggered, turned on",STRING(pev->targetname)); |
|
if (pev->target) |
|
{ |
|
ALERT(at_console,": firing \"%s\"",STRING(pev->target)); |
|
if (pev->noise1) |
|
ALERT(at_console," and \"%s\"",STRING(pev->noise1)); |
|
} |
|
else if (pev->noise1) |
|
ALERT(at_console,": firing \"%s\"", STRING(pev->noise1)); |
|
ALERT(at_console,".\n"); |
|
} |
|
FireTargets(STRING(pev->target),pActivator,this,USE_ON,0); |
|
FireTargets(STRING(pev->noise1),pActivator,this,USE_TOGGLE,0); |
|
DontThink(); |
|
} |
|
break; |
|
case STATE_IN_USE: |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
void CEnvState::Think( void ) |
|
{ |
|
if (m_iState == STATE_TURN_ON) |
|
{ |
|
m_iState = STATE_ON; |
|
if (pev->spawnflags & SF_ENVSTATE_DEBUG) |
|
{ |
|
ALERT(at_console,"DEBUG: env_state \"%s\" turned itself on",STRING(pev->targetname)); |
|
if (pev->target) |
|
{ |
|
ALERT(at_console,": firing %s",STRING(pev->target)); |
|
if (pev->noise1) |
|
ALERT(at_console," and %s",STRING(pev->noise1)); |
|
} |
|
else if (pev->noise1) |
|
ALERT(at_console,": firing %s",STRING(pev->noise1)); |
|
ALERT(at_console,".\n"); |
|
} |
|
FireTargets(STRING(pev->target),this,this,USE_ON,0); |
|
FireTargets(STRING(pev->noise1),this,this,USE_TOGGLE,0); |
|
} |
|
else if (m_iState == STATE_TURN_OFF) |
|
{ |
|
m_iState = STATE_OFF; |
|
if (pev->spawnflags & SF_ENVSTATE_DEBUG) |
|
{ |
|
ALERT(at_console,"DEBUG: env_state \"%s\" turned itself off",STRING(pev->targetname)); |
|
if (pev->target) |
|
ALERT(at_console,": firing %s",STRING(pev->target)); |
|
if (pev->noise2) |
|
ALERT(at_console," and %s",STRING(pev->noise2)); |
|
else if (pev->noise2) |
|
ALERT(at_console,": firing %s",STRING(pev->noise2)); |
|
ALERT(at_console,".\n"); |
|
} |
|
FireTargets(STRING(pev->target),this,this,USE_OFF,0); |
|
FireTargets(STRING(pev->noise2),this,this,USE_TOGGLE,0); |
|
} |
|
} |
|
|
|
|
|
//=========================== |
|
//LRC- the evil multisource... |
|
//=========================== |
|
|
|
TYPEDESCRIPTION CMultiSource::m_SaveData[] = |
|
{ |
|
//!!!BUGBUG FIX |
|
DEFINE_ARRAY( CMultiSource, m_rgEntities, FIELD_EHANDLE, MS_MAX_TARGETS ), |
|
DEFINE_ARRAY( CMultiSource, m_rgTriggered, FIELD_INTEGER, MS_MAX_TARGETS ), |
|
DEFINE_FIELD( CMultiSource, m_iTotal, FIELD_INTEGER ), |
|
DEFINE_FIELD( CMultiSource, m_globalstate, FIELD_STRING ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CMultiSource, CPointEntity ) |
|
|
|
LINK_ENTITY_TO_CLASS( multisource, CMultiSource ) |
|
|
|
// |
|
// Cache user-entity-field values until spawn is called. |
|
// |
|
void CMultiSource::KeyValue( KeyValueData *pkvd ) |
|
{ |
|
if( FStrEq( pkvd->szKeyName, "style" ) || |
|
FStrEq( pkvd->szKeyName, "height" ) || |
|
FStrEq( pkvd->szKeyName, "killtarget" ) || |
|
FStrEq( pkvd->szKeyName, "value1" ) || |
|
FStrEq( pkvd->szKeyName, "value2" ) || |
|
FStrEq( pkvd->szKeyName, "value3" ) ) |
|
pkvd->fHandled = TRUE; |
|
else if( FStrEq( pkvd->szKeyName, "globalstate" ) ) |
|
{ |
|
m_globalstate = ALLOC_STRING( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else |
|
CPointEntity::KeyValue( pkvd ); |
|
} |
|
|
|
#define SF_MULTI_FIREONCLOSE 1 |
|
#define SF_MULTI_INIT 2 |
|
|
|
void CMultiSource::Spawn() |
|
{ |
|
// set up think for later registration |
|
|
|
pev->solid = SOLID_NOT; |
|
pev->movetype = MOVETYPE_NONE; |
|
SetNextThink( 0.1f ); |
|
pev->spawnflags |= SF_MULTI_INIT; // Until it's initialized |
|
SetThink( &CMultiSource::Register ); |
|
} |
|
|
|
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 ) |
|
{ |
|
if (pCaller->pev->targetname) |
|
ALERT(at_console, "multisource \"%s\": Used by non-member %s \"%s\"\n", STRING(pev->targetname), STRING(pCaller->pev->classname), STRING(pCaller->pev->targetname)); |
|
else |
|
ALERT(at_console, "multisource \"%s\": Used by non-member %s\n", STRING(pev->targetname), STRING(pCaller->pev->classname)); |
|
return; |
|
} |
|
|
|
// CONSIDER: a Use input to the multisource always toggles. Could check useType for ON/OFF/TOGGLE |
|
// LRC- On could be meaningful. Off, sadly, can't work in the obvious manner. |
|
// LRC (09/06/01)- er... why not? |
|
// LRC (28/04/02)- that depends what the "obvious" manner is. |
|
|
|
// store the state before the change, so we can compare it to the new state |
|
STATE s = GetState(); |
|
|
|
// do the change |
|
m_rgTriggered[i - 1] ^= 1; |
|
|
|
// did we change state? |
|
if ( s == GetState() ) |
|
return; |
|
|
|
if ( s == STATE_ON && pev->netname) |
|
{ |
|
// the change disabled me and I have a "fire on disable" field |
|
ALERT( at_aiconsole, "Multisource %s deactivated (%d inputs)\n", STRING(pev->targetname), m_iTotal ); |
|
if ( m_globalstate ) |
|
FireTargets( STRING(pev->netname), NULL, this, USE_OFF, 0 ); |
|
else |
|
FireTargets( STRING(pev->netname), NULL, this, USE_TOGGLE, 0 ); |
|
} |
|
else if ( s == STATE_OFF ) |
|
{ |
|
// the change activated me |
|
ALERT( at_aiconsole, "Multisource %s enabled (%d inputs)\n", STRING( pev->targetname ), m_iTotal ); |
|
USE_TYPE useType = USE_TOGGLE; |
|
if( m_globalstate ) |
|
useType = USE_ON; |
|
SUB_UseTargets( NULL, useType, 0 ); |
|
} |
|
} |
|
|
|
|
|
//LRC- while we're in STATE_OFF, mastered entities can't do anything. |
|
STATE CMultiSource::GetState( void ) |
|
{ |
|
// Is everything triggered? |
|
int i = 0; |
|
|
|
// Still initializing? |
|
if( pev->spawnflags & SF_MULTI_INIT ) |
|
return STATE_OFF; |
|
|
|
while( i < m_iTotal ) |
|
{ |
|
if( m_rgTriggered[i] == 0 ) |
|
break; |
|
i++; |
|
} |
|
|
|
if( i == m_iTotal ) |
|
{ |
|
if( !m_globalstate || gGlobalState.EntityGetState( m_globalstate ) == GLOBAL_ON ) |
|
return STATE_ON; |
|
} |
|
|
|
return STATE_OFF; |
|
} |
|
/* |
|
void CMultiSource::Register( void ) |
|
{ |
|
edict_t *pentTarget = 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 (pev->targetname) |
|
pentTarget = FIND_ENTITY_BY_STRING( NULL, "target", STRING( pev->targetname ) ); |
|
|
|
while( !FNullEnt( pentTarget ) && ( m_iTotal < MS_MAX_TARGETS ) ) |
|
{ |
|
CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); |
|
if( pTarget ) |
|
m_rgEntities[m_iTotal++] = pTarget; |
|
|
|
pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "target", STRING( pev->targetname ) ); |
|
} |
|
|
|
pentTarget = FIND_ENTITY_BY_STRING( NULL, "classname", "multi_manager" ); |
|
while( !FNullEnt( pentTarget ) && ( m_iTotal < MS_MAX_TARGETS ) ) |
|
{ |
|
CBaseEntity *pTarget = CBaseEntity::Instance( pentTarget ); |
|
if( pTarget && pTarget->HasTarget( pev->targetname ) ) |
|
m_rgEntities[m_iTotal++] = pTarget; |
|
|
|
pentTarget = FIND_ENTITY_BY_STRING( pentTarget, "classname", "multi_manager" ); |
|
} |
|
pev->spawnflags &= ~SF_MULTI_INIT; |
|
} |
|
*/ |
|
void CMultiSource::Register(void) |
|
{ |
|
m_iTotal = 0; |
|
memset( m_rgEntities, 0, MS_MAX_TARGETS * sizeof(EHANDLE) ); |
|
|
|
SetThink(&CMultiSource::SUB_DoNothing); |
|
|
|
// search for all entities which target this multisource (pev->targetname) |
|
|
|
CBaseEntity *pTarget = UTIL_FindEntityByTarget( NULL, STRING(pev->targetname) ); |
|
while (pTarget && (m_iTotal < MS_MAX_TARGETS)) |
|
{ |
|
m_rgEntities[m_iTotal++] = pTarget; |
|
|
|
pTarget = UTIL_FindEntityByTarget( pTarget, STRING(pev->targetname)); |
|
} |
|
|
|
pTarget = UTIL_FindEntityByClassname(NULL, "multi_manager"); |
|
while (pTarget && (m_iTotal < MS_MAX_TARGETS)) |
|
{ |
|
if ( pTarget->HasTarget(pev->targetname) ) |
|
m_rgEntities[m_iTotal++] = pTarget; |
|
|
|
pTarget = UTIL_FindEntityByClassname( pTarget, "multi_manager" ); |
|
} |
|
|
|
if (m_iTotal >= MS_MAX_TARGETS) |
|
{ |
|
ALERT(at_console,"WARNING: There are too many entities targetting multisource \"%s\". (limit is %d)\n", STRING(pev->targetname), MS_MAX_TARGETS); |
|
} |
|
|
|
pev->spawnflags &= ~SF_MULTI_INIT; |
|
} |
|
|
|
//=================================== |
|
// func_button (= CBaseButton) |
|
//=================================== |
|
|
|
//LRC - moved here from cbase.h to use the spawnflags defined in this file |
|
// Buttons that don't take damage can be IMPULSE used |
|
int CBaseButton::ObjectCaps( void ) |
|
{ |
|
return (CBaseToggle:: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | |
|
(pev->takedamage?0:FCAP_IMPULSE_USE) | |
|
(pev->spawnflags & SF_BUTTON_ONLYDIRECT?FCAP_ONLYDIRECT_USE:0); |
|
} |
|
|
|
// CBaseButton |
|
TYPEDESCRIPTION CBaseButton::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CBaseButton, m_fStayPushed, FIELD_BOOLEAN ), |
|
DEFINE_FIELD( CBaseButton, m_fRotating, FIELD_BOOLEAN ), |
|
|
|
DEFINE_FIELD( CBaseButton, m_sounds, FIELD_INTEGER ), |
|
DEFINE_FIELD( CBaseButton, m_bLockedSound, FIELD_CHARACTER ), |
|
DEFINE_FIELD( CBaseButton, m_bLockedSentence, FIELD_CHARACTER ), |
|
DEFINE_FIELD( CBaseButton, m_bUnlockedSound, FIELD_CHARACTER ), |
|
DEFINE_FIELD( CBaseButton, m_bUnlockedSentence, FIELD_CHARACTER ), |
|
DEFINE_FIELD( CBaseButton, m_strChangeTarget, FIELD_STRING ), |
|
//DEFINE_FIELD( CBaseButton, m_ls, FIELD_??? ), // This is restored in Precache() |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CBaseButton, CBaseToggle ) |
|
|
|
void CBaseButton::Precache( void ) |
|
{ |
|
const char *pszSound; |
|
|
|
if( FBitSet( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state |
|
{ |
|
PRECACHE_SOUND( "buttons/spark1.wav" ); |
|
PRECACHE_SOUND( "buttons/spark2.wav" ); |
|
PRECACHE_SOUND( "buttons/spark3.wav" ); |
|
PRECACHE_SOUND( "buttons/spark4.wav" ); |
|
PRECACHE_SOUND( "buttons/spark5.wav" ); |
|
PRECACHE_SOUND( "buttons/spark6.wav" ); |
|
} |
|
|
|
// get door button sounds, for doors which require buttons to open |
|
if( m_bLockedSound ) |
|
{ |
|
pszSound = ButtonSound( (int)m_bLockedSound ); |
|
PRECACHE_SOUND( pszSound ); |
|
m_ls.sLockedSound = MAKE_STRING( pszSound ); |
|
} |
|
|
|
if( m_bUnlockedSound ) |
|
{ |
|
pszSound = ButtonSound( (int)m_bUnlockedSound ); |
|
PRECACHE_SOUND( pszSound ); |
|
m_ls.sUnlockedSound = MAKE_STRING( pszSound ); |
|
} |
|
|
|
// get sentence group names, for doors which are directly 'touched' to open |
|
switch( m_bLockedSentence ) |
|
{ |
|
case 1: // access denied |
|
m_ls.sLockedSentence = MAKE_STRING( "NA" ); |
|
break; |
|
case 2: // security lockout |
|
m_ls.sLockedSentence = MAKE_STRING( "ND" ); |
|
break; |
|
case 3: // blast door |
|
m_ls.sLockedSentence = MAKE_STRING( "NF" ); |
|
break; |
|
case 4: // fire door |
|
m_ls.sLockedSentence = MAKE_STRING( "NFIRE" ); |
|
break; |
|
case 5: // chemical door |
|
m_ls.sLockedSentence = MAKE_STRING( "NCHEM" ); |
|
break; |
|
case 6: // radiation door |
|
m_ls.sLockedSentence = MAKE_STRING( "NRAD" ); |
|
break; |
|
case 7: // gen containment |
|
m_ls.sLockedSentence = MAKE_STRING( "NCON" ); |
|
break; |
|
case 8: // maintenance door |
|
m_ls.sLockedSentence = MAKE_STRING( "NH" ); |
|
break; |
|
case 9: // broken door |
|
m_ls.sLockedSentence = MAKE_STRING( "NG" ); |
|
break; |
|
default: |
|
m_ls.sLockedSentence = 0; |
|
break; |
|
} |
|
|
|
switch( m_bUnlockedSentence ) |
|
{ |
|
case 1: // access granted |
|
m_ls.sUnlockedSentence = MAKE_STRING( "EA" ); |
|
break; |
|
case 2: // security door |
|
m_ls.sUnlockedSentence = MAKE_STRING( "ED" ); |
|
break; |
|
case 3: // blast door |
|
m_ls.sUnlockedSentence = MAKE_STRING( "EF" ); |
|
break; |
|
case 4: // fire door |
|
m_ls.sUnlockedSentence = MAKE_STRING( "EFIRE" ); |
|
break; |
|
case 5: // chemical door |
|
m_ls.sUnlockedSentence = MAKE_STRING( "ECHEM" ); |
|
break; |
|
case 6: // radiation door |
|
m_ls.sUnlockedSentence = MAKE_STRING( "ERAD" ); |
|
break; |
|
case 7: // gen containment |
|
m_ls.sUnlockedSentence = MAKE_STRING( "ECON" ); |
|
break; |
|
case 8: // maintenance door |
|
m_ls.sUnlockedSentence = MAKE_STRING( "EH" ); |
|
break; |
|
default: |
|
m_ls.sUnlockedSentence = 0; |
|
break; |
|
} |
|
} |
|
|
|
// |
|
// Cache user-entity-field values until spawn is called. |
|
// |
|
void CBaseButton::KeyValue( KeyValueData *pkvd ) |
|
{ |
|
if( FStrEq( pkvd->szKeyName, "changetarget" ) ) |
|
{ |
|
m_strChangeTarget = ALLOC_STRING( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else if( FStrEq( pkvd->szKeyName, "locked_sound" ) ) |
|
{ |
|
m_bLockedSound = atoi( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else if( FStrEq( pkvd->szKeyName, "locked_sentence" ) ) |
|
{ |
|
m_bLockedSentence = atoi( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else if( FStrEq( pkvd->szKeyName, "unlocked_sound" ) ) |
|
{ |
|
m_bUnlockedSound = atoi( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else if( FStrEq( pkvd->szKeyName, "unlocked_sentence" ) ) |
|
{ |
|
m_bUnlockedSentence = atoi( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else if( FStrEq( pkvd->szKeyName, "sounds" ) ) |
|
{ |
|
m_sounds = atoi( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else |
|
CBaseToggle::KeyValue( pkvd ); |
|
} |
|
|
|
// |
|
// ButtonShot |
|
// |
|
int CBaseButton::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) |
|
{ |
|
BUTTON_CODE code = ButtonResponseToTouch(); |
|
|
|
if( code == BUTTON_NOTHING ) |
|
return 0; |
|
// Temporarily disable the touch function, until movement is finished. |
|
SetTouch( NULL ); |
|
|
|
m_hActivator = CBaseEntity::Instance( pevAttacker ); |
|
if( m_hActivator == 0 ) |
|
return 0; |
|
|
|
if( code == BUTTON_RETURN ) |
|
{ |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, STRING( pev->noise ), 1, ATTN_NORM ); |
|
|
|
// Toggle buttons fire when they get back to their "home" position |
|
if( !( pev->spawnflags & SF_BUTTON_TOGGLE ) ) |
|
SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); |
|
ButtonReturn(); |
|
} |
|
else // code == BUTTON_ACTIVATE |
|
ButtonActivate(); |
|
|
|
return 0; |
|
} |
|
|
|
/*QUAKED func_button (0 .5 .8) ? |
|
When a button is touched, it moves some distance in the direction of it's angle, |
|
triggers all of it's targets, waits some time, then returns to it's original position |
|
where it can be triggered again. |
|
|
|
"angle" determines the opening direction |
|
"target" all entities with a matching targetname will be used |
|
"speed" override the default 40 speed |
|
"wait" override the default 1 second wait (-1 = never return) |
|
"lip" override the default 4 pixel lip remaining at end of move |
|
"health" if set, the button must be killed instead of touched |
|
"sounds" |
|
0) steam metal |
|
1) wooden clunk |
|
2) metallic click |
|
3) in-out |
|
*/ |
|
|
|
LINK_ENTITY_TO_CLASS( func_button, CBaseButton ) |
|
|
|
void CBaseButton::Spawn() |
|
{ |
|
const char *pszSound; |
|
|
|
//---------------------------------------------------- |
|
//determine sounds for buttons |
|
//a sound of 0 should not make a sound |
|
//---------------------------------------------------- |
|
pszSound = ButtonSound( m_sounds ); |
|
PRECACHE_SOUND( pszSound ); |
|
pev->noise = MAKE_STRING( pszSound ); |
|
|
|
Precache(); |
|
|
|
if( FBitSet( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) )// this button should spark in OFF state |
|
{ |
|
SetThink( &CBaseButton::ButtonSpark ); |
|
SetNextThink( 0.5f );// no hurry, make sure everything else spawns |
|
} |
|
|
|
SetMovedir( pev ); |
|
|
|
pev->movetype = MOVETYPE_PUSH; |
|
if ( FBitSet ( pev->spawnflags, SF_BUTTON_NOT_SOLID ) ) |
|
{ |
|
pev->solid = SOLID_NOT; |
|
pev->skin = CONTENTS_EMPTY; |
|
} |
|
else |
|
{ |
|
pev->solid = SOLID_BSP; |
|
} |
|
SET_MODEL( ENT( pev ), STRING( pev->model ) ); |
|
|
|
|
|
//LRC |
|
if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "z"); |
|
else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "a"); |
|
|
|
if( pev->speed == 0.0f ) |
|
pev->speed = 40.0f; |
|
|
|
if( pev->health > 0 ) |
|
{ |
|
pev->takedamage = DAMAGE_YES; |
|
} |
|
|
|
if( m_flWait == 0.0f ) |
|
m_flWait = 1.0f; |
|
if( m_flLip == 0.0f ) |
|
m_flLip = 4.0f; |
|
|
|
m_toggle_state = TS_AT_BOTTOM; |
|
m_vecPosition1 = pev->origin; |
|
// Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big |
|
m_vecPosition2 = m_vecPosition1 + ( pev->movedir * ( fabs( pev->movedir.x * ( pev->size.x - 2.0f ) ) + fabs( pev->movedir.y * ( pev->size.y - 2.0f ) ) + fabs( pev->movedir.z * ( pev->size.z - 2.0f ) ) - m_flLip ) ); |
|
|
|
// Is this a non-moving button? |
|
if( ( ( m_vecPosition2 - m_vecPosition1 ).Length() < 1.0f ) || ( pev->spawnflags & SF_BUTTON_DONTMOVE ) ) |
|
m_vecPosition2 = m_vecPosition1; |
|
|
|
m_fStayPushed = m_flWait == -1.0f ? TRUE : FALSE; |
|
m_fRotating = FALSE; |
|
|
|
// if the button is flagged for USE button activation only, take away it's touch function and add a use function |
|
if( FBitSet( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // touchable button |
|
{ |
|
SetTouch( &CBaseButton::ButtonTouch ); |
|
if ( !FBitSet ( pev->spawnflags, SF_BUTTON_USEKEY ) ) |
|
SetUse(&CBaseButton:: ButtonUse_IgnorePlayer ); |
|
else |
|
SetUse(&CBaseButton:: ButtonUse ); |
|
} |
|
else |
|
{ |
|
SetTouch( NULL ); |
|
if ( FBitSet ( pev->spawnflags, SF_BUTTON_USEKEY ) ) |
|
SetUse(&CBaseButton:: ButtonUse_IgnorePlayer ); |
|
else |
|
SetUse( &CBaseButton::ButtonUse ); |
|
} |
|
} |
|
|
|
//LRC |
|
void CBaseButton :: PostSpawn( void ) |
|
{ |
|
if (m_pMoveWith) |
|
m_vecPosition1 = pev->origin - m_pMoveWith->pev->origin; |
|
else |
|
m_vecPosition1 = pev->origin; |
|
// Subtract 2 from size because the engine expands bboxes by 1 in all directions |
|
m_vecPosition2 = m_vecPosition1 + (pev->movedir * (fabs( pev->movedir.x * (pev->size.x-2) ) + fabs( pev->movedir.y * (pev->size.y-2) ) + fabs( pev->movedir.z * (pev->size.z-2) ) - m_flLip)); |
|
|
|
// Is this a non-moving button? |
|
if ( ((m_vecPosition2 - m_vecPosition1).Length() < 1) || (pev->spawnflags & SF_BUTTON_DONTMOVE) ) |
|
m_vecPosition2 = m_vecPosition1; |
|
} |
|
|
|
// Button sound table. |
|
// Also used by CBaseDoor to get 'touched' door lock/unlock sounds |
|
|
|
const char *ButtonSound( int sound ) |
|
{ |
|
const char *pszSound; |
|
|
|
switch( sound ) |
|
{ |
|
case 0: |
|
pszSound = "common/null.wav"; |
|
break; |
|
case 1: |
|
pszSound = "buttons/button1.wav"; |
|
break; |
|
case 2: |
|
pszSound = "buttons/button2.wav"; |
|
break; |
|
case 3: |
|
pszSound = "buttons/button3.wav"; |
|
break; |
|
case 4: |
|
pszSound = "buttons/button4.wav"; |
|
break; |
|
case 5: |
|
pszSound = "buttons/button5.wav"; |
|
break; |
|
case 6: |
|
pszSound = "buttons/button6.wav"; |
|
break; |
|
case 7: |
|
pszSound = "buttons/button7.wav"; |
|
break; |
|
case 8: |
|
pszSound = "buttons/button8.wav"; |
|
break; |
|
case 9: |
|
pszSound = "buttons/button9.wav"; |
|
break; |
|
case 10: |
|
pszSound = "buttons/button10.wav"; |
|
break; |
|
case 11: |
|
pszSound = "buttons/button11.wav"; |
|
break; |
|
case 12: |
|
pszSound = "buttons/latchlocked1.wav"; |
|
break; |
|
case 13: |
|
pszSound = "buttons/latchunlocked1.wav"; |
|
break; |
|
case 14: |
|
pszSound = "buttons/lightswitch2.wav"; |
|
break; |
|
|
|
// next 6 slots reserved for any additional sliding button sounds we may add |
|
|
|
case 21: |
|
pszSound = "buttons/lever1.wav"; |
|
break; |
|
case 22: |
|
pszSound = "buttons/lever2.wav"; |
|
break; |
|
case 23: |
|
pszSound = "buttons/lever3.wav"; |
|
break; |
|
case 24: |
|
pszSound = "buttons/lever4.wav"; |
|
break; |
|
case 25: |
|
pszSound = "buttons/lever5.wav"; |
|
break; |
|
default: |
|
pszSound = "buttons/button9.wav"; |
|
break; |
|
} |
|
|
|
return pszSound; |
|
} |
|
|
|
// |
|
// Makes flagged buttons spark when turned off |
|
// |
|
void DoSpark( entvars_t *pev, const Vector &location ) |
|
{ |
|
Vector tmp = location + pev->size * 0.5f; |
|
UTIL_Sparks( tmp ); |
|
|
|
float flVolume = RANDOM_FLOAT( 0.25f, 0.75f ) * 0.4f;//random volume range |
|
switch( (int)( RANDOM_FLOAT( 0.0f, 1.0f ) * 6.0f ) ) |
|
{ |
|
case 0: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "buttons/spark1.wav", flVolume, ATTN_NORM ); |
|
break; |
|
case 1: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "buttons/spark2.wav", flVolume, ATTN_NORM ); |
|
break; |
|
case 2: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "buttons/spark3.wav", flVolume, ATTN_NORM ); |
|
break; |
|
case 3: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "buttons/spark4.wav", flVolume, ATTN_NORM ); |
|
break; |
|
case 4: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "buttons/spark5.wav", flVolume, ATTN_NORM ); |
|
break; |
|
case 5: |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, "buttons/spark6.wav", flVolume, ATTN_NORM ); |
|
break; |
|
} |
|
} |
|
|
|
void CBaseButton::ButtonSpark( void ) |
|
{ |
|
SetThink( &CBaseButton::ButtonSpark ); |
|
SetNextThink( 0.1f + RANDOM_FLOAT( 0.0f, 1.5f ) );// spark again at random interval |
|
|
|
DoSpark( pev, pev->mins ); |
|
} |
|
|
|
// |
|
// Button's Use function |
|
// |
|
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; |
|
|
|
m_hActivator = pActivator; |
|
if( m_toggle_state == TS_AT_TOP ) |
|
{ |
|
if( !m_fStayPushed && FBitSet( pev->spawnflags, SF_BUTTON_TOGGLE ) ) |
|
{ |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, STRING( pev->noise ), 1.0f, ATTN_NORM ); |
|
|
|
//SUB_UseTargets( m_eoActivator ); |
|
ButtonReturn(); |
|
} |
|
} |
|
else |
|
ButtonActivate(); |
|
} |
|
|
|
//LRC - they had it set up so that a touch-only button couldn't even be triggered!? |
|
void CBaseButton::ButtonUse_IgnorePlayer ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
if ( !pCaller || !pCaller->IsPlayer() ) |
|
ButtonUse( pActivator, pCaller, useType, value ); |
|
} |
|
|
|
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 && !FBitSet(pev->spawnflags, SF_BUTTON_TOGGLE ) ) ) |
|
return BUTTON_NOTHING; |
|
|
|
if( m_toggle_state == TS_AT_TOP ) |
|
{ |
|
if( ( FBitSet( pev->spawnflags, SF_BUTTON_TOGGLE ) ) && !m_fStayPushed ) |
|
{ |
|
return BUTTON_RETURN; |
|
} |
|
} |
|
else |
|
return BUTTON_ACTIVATE; |
|
|
|
return BUTTON_NOTHING; |
|
} |
|
|
|
// |
|
// Touching a button simply "activates" it. |
|
// |
|
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 ) ) |
|
{ |
|
// play button locked sound |
|
PlayLockSounds( pev, &m_ls, TRUE, TRUE ); |
|
return; |
|
} |
|
|
|
// Temporarily disable the touch function, until movement is finished. |
|
SetTouch( NULL ); |
|
|
|
if( code == BUTTON_RETURN ) |
|
{ |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, STRING( pev->noise ), 1, ATTN_NORM ); |
|
SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); |
|
ButtonReturn(); |
|
} |
|
else // code == BUTTON_ACTIVATE |
|
ButtonActivate(); |
|
} |
|
|
|
// |
|
// Starts the button moving "in/up". |
|
// |
|
void CBaseButton::ButtonActivate() |
|
{ |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, STRING( pev->noise ), 1, ATTN_NORM ); |
|
|
|
if( !UTIL_IsMasterTriggered( m_sMaster, m_hActivator ) ) |
|
{ |
|
// button is locked, play locked sound |
|
PlayLockSounds( pev, &m_ls, TRUE, TRUE ); |
|
return; |
|
} |
|
else |
|
{ |
|
// button is unlocked, play unlocked sound |
|
PlayLockSounds( pev, &m_ls, FALSE, TRUE ); |
|
} |
|
|
|
ASSERT( m_toggle_state == TS_AT_BOTTOM ); |
|
m_toggle_state = TS_GOING_UP; |
|
|
|
//LRC - unhelpfully, SF_BUTTON_DONTMOVE is the same value as |
|
// SF_ROTBUTTON_NOTSOLID, so we have to assume that a rotbutton will |
|
// never be DONTMOVE. |
|
if (pev->spawnflags & SF_BUTTON_DONTMOVE && !m_fRotating) |
|
{ |
|
TriggerAndWait(); |
|
} |
|
else |
|
{ |
|
SetMoveDone( &CBaseButton::TriggerAndWait ); |
|
if( !m_fRotating ) |
|
LinearMove( m_vecPosition2, pev->speed ); |
|
else |
|
AngularMove( m_vecAngle2, pev->speed ); |
|
} |
|
} |
|
|
|
// |
|
// Button has reached the "in/up" position. Activate its "targets", and pause before "popping out". |
|
// |
|
void CBaseButton::TriggerAndWait( void ) |
|
{ |
|
ASSERT( m_toggle_state == TS_GOING_UP ); |
|
|
|
if( !UTIL_IsMasterTriggered( m_sMaster, m_hActivator ) ) |
|
return; |
|
|
|
m_toggle_state = TS_AT_TOP; |
|
|
|
pev->frame = 1; // use alternate textures |
|
//LRC |
|
if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "a"); |
|
else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "z"); |
|
|
|
SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); |
|
|
|
// If button automatically comes back out, start it moving out. |
|
// Else re-instate touch method |
|
if( m_fStayPushed || FBitSet( pev->spawnflags, SF_BUTTON_TOGGLE ) ) |
|
{ |
|
if( !FBitSet( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! |
|
{ |
|
// ALL buttons are now use only |
|
SetTouch( NULL ); |
|
} |
|
else |
|
SetTouch( &CBaseButton::ButtonTouch ); |
|
} |
|
else |
|
{ |
|
SetThink( &CBaseButton::ButtonReturn ); |
|
if ( m_flWait ) |
|
{ |
|
SetNextThink( m_flWait ); |
|
} |
|
else |
|
{ |
|
ButtonReturn(); |
|
} |
|
} |
|
} |
|
|
|
|
|
// |
|
// Starts the button moving "out/down". |
|
// |
|
void CBaseButton::ButtonReturn( void ) |
|
{ |
|
ASSERT( m_toggle_state == TS_AT_TOP ); |
|
m_toggle_state = TS_GOING_DOWN; |
|
|
|
pev->frame = 0; // use normal textures |
|
|
|
//LRC |
|
if (m_iStyle >= 32) LIGHT_STYLE(m_iStyle, "z"); |
|
else if (m_iStyle <= -32) LIGHT_STYLE(-m_iStyle, "a"); |
|
|
|
if (pev->spawnflags & SF_BUTTON_DONTMOVE) |
|
{ |
|
ButtonBackHome(); |
|
} |
|
else |
|
{ |
|
SetMoveDone( &CBaseButton::ButtonBackHome ); |
|
if( !m_fRotating ) |
|
LinearMove( m_vecPosition1, pev->speed ); |
|
else |
|
AngularMove( m_vecAngle1, pev->speed ); |
|
} |
|
} |
|
|
|
|
|
// |
|
// Button has returned to start state. Quiesce it. |
|
// |
|
void CBaseButton::ButtonBackHome( void ) |
|
{ |
|
ASSERT( m_toggle_state == TS_GOING_DOWN ); |
|
m_toggle_state = TS_AT_BOTTOM; |
|
|
|
if( FBitSet( pev->spawnflags, SF_BUTTON_TOGGLE ) ) |
|
{ |
|
//EMIT_SOUND( ENT( pev ), CHAN_VOICE, STRING( pev->noise ), 1, ATTN_NORM ); |
|
|
|
SUB_UseTargets( m_hActivator, USE_TOGGLE, 0 ); |
|
} |
|
|
|
if( !FStringNull( pev->target ) ) |
|
{ |
|
CBaseEntity *pTarget = NULL; |
|
for( ;; ) |
|
{ |
|
pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(pev->target), m_hActivator); |
|
|
|
if (FNullEnt(pTarget)) |
|
break; |
|
|
|
if (!FClassnameIs(pTarget->pev, "multisource")) |
|
// LRC- hmm... I see. On returning, a button will only turn off multisources. |
|
continue; |
|
|
|
pTarget->Use( m_hActivator, this, USE_TOGGLE, 0 ); |
|
} |
|
} |
|
|
|
// Re-instate touch method, movement cycle is complete. |
|
if( !FBitSet( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) // this button only works if USED, not touched! |
|
{ |
|
// All buttons are now use only |
|
SetTouch( NULL ); |
|
} |
|
else |
|
SetTouch( &CBaseButton::ButtonTouch ); |
|
|
|
// reset think for a sparking button |
|
if( FBitSet( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) ) |
|
{ |
|
SetThink( &CBaseButton::ButtonSpark ); |
|
SetNextThink( 0.5f );// no hurry. |
|
} |
|
else |
|
{ |
|
DontThink(); |
|
} |
|
} |
|
|
|
// |
|
// Rotating button (aka "lever") |
|
// |
|
class CRotButton : public CBaseButton |
|
{ |
|
public: |
|
void Spawn( void ); |
|
void PostSpawn( void ) {} // don't use the moveWith fix from CBaseButton |
|
virtual void KeyValue( KeyValueData* pkvd); |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( func_rot_button, CRotButton ) |
|
|
|
void CRotButton::KeyValue( KeyValueData *pkvd ) |
|
{ |
|
if (FStrEq(pkvd->szKeyName, "axes")) |
|
{ |
|
UTIL_StringToVector( (float*)(pev->movedir), pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else |
|
CBaseButton::KeyValue( pkvd ); |
|
} |
|
|
|
void CRotButton::Spawn( void ) |
|
{ |
|
const char *pszSound; |
|
//---------------------------------------------------- |
|
//determine sounds for buttons |
|
//a sound of 0 should not make a sound |
|
//---------------------------------------------------- |
|
pszSound = ButtonSound( m_sounds ); |
|
PRECACHE_SOUND( pszSound ); |
|
pev->noise = MAKE_STRING( pszSound ); |
|
|
|
// set the axis of rotation |
|
CBaseToggle::AxisDir( pev ); |
|
|
|
// check for clockwise rotation |
|
if( FBitSet( pev->spawnflags, SF_DOOR_ROTATE_BACKWARDS ) ) |
|
pev->movedir = pev->movedir * -1.0f; |
|
|
|
pev->movetype = MOVETYPE_PUSH; |
|
|
|
if( pev->spawnflags & SF_ROTBUTTON_NOTSOLID ) |
|
pev->solid = SOLID_NOT; |
|
else |
|
pev->solid = SOLID_BSP; |
|
|
|
SET_MODEL( ENT( pev ), STRING( pev->model ) ); |
|
|
|
if( pev->speed == 0.0f ) |
|
pev->speed = 40.0f; |
|
|
|
if( m_flWait == 0 ) |
|
m_flWait = 1; |
|
|
|
if( pev->health > 0 ) |
|
{ |
|
pev->takedamage = DAMAGE_YES; |
|
} |
|
|
|
m_toggle_state = TS_AT_BOTTOM; |
|
m_vecAngle1 = pev->angles; |
|
m_vecAngle2 = pev->angles + pev->movedir * m_flMoveDistance; |
|
ASSERTSZ( m_vecAngle1 != m_vecAngle2, "rotating button start/end positions are equal" ); |
|
|
|
m_fStayPushed = m_flWait == -1.0f ? TRUE : FALSE; |
|
m_fRotating = TRUE; |
|
|
|
// if the button is flagged for USE button activation only, take away it's touch function and add a use function |
|
if( !FBitSet( pev->spawnflags, SF_BUTTON_TOUCH_ONLY ) ) |
|
{ |
|
SetTouch( NULL ); |
|
if ( FBitSet ( pev->spawnflags, SF_BUTTON_USEKEY ) ) |
|
SetUse(&CRotButton:: ButtonUse_IgnorePlayer ); |
|
else |
|
SetUse(&CRotButton:: ButtonUse ); |
|
} |
|
else // touchable button |
|
{ |
|
SetTouch(&CRotButton:: ButtonTouch ); |
|
if ( !FBitSet ( pev->spawnflags, SF_BUTTON_USEKEY ) ) |
|
SetUse(&CRotButton:: ButtonUse_IgnorePlayer ); |
|
else |
|
SetUse(&CRotButton:: ButtonUse ); |
|
} |
|
|
|
//SetTouch( &ButtonTouch ); |
|
} |
|
|
|
// Make this button behave like a door (HACKHACK) |
|
// This will disable use and make the button solid |
|
// rotating buttons were made SOLID_NOT by default since their were some |
|
// collision problems with them... |
|
#define SF_MOMENTARY_DOOR 0x0001 |
|
|
|
class CMomentaryRotButton : public CBaseToggle |
|
{ |
|
public: |
|
void Spawn( void ); |
|
void KeyValue( KeyValueData *pkvd ); |
|
virtual int ObjectCaps( void ) |
|
{ |
|
int flags = CBaseToggle::ObjectCaps() & ( ~FCAP_ACROSS_TRANSITION ); |
|
if( pev->spawnflags & SF_MOMENTARY_DOOR ) |
|
return flags; |
|
return flags | FCAP_CONTINUOUS_USE; |
|
} |
|
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); |
|
void EXPORT Off( void ); |
|
void EXPORT Return( void ); |
|
void UpdateSelf( float value ); |
|
void UpdateSelfReturn( float value ); |
|
void UpdateAllButtons( float value, int start ); |
|
|
|
void PlaySound( void ); |
|
void UpdateTarget( float value ); |
|
|
|
static CMomentaryRotButton *Instance( edict_t *pent ) { return (CMomentaryRotButton *)GET_PRIVATE( pent ); }; |
|
virtual int Save( CSave &save ); |
|
virtual int Restore( CRestore &restore ); |
|
static TYPEDESCRIPTION m_SaveData[]; |
|
|
|
int m_lastUsed; |
|
int m_direction; |
|
float m_returnSpeed; |
|
vec3_t m_start; |
|
vec3_t m_end; |
|
int m_sounds; |
|
}; |
|
|
|
TYPEDESCRIPTION CMomentaryRotButton::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CMomentaryRotButton, m_lastUsed, FIELD_INTEGER ), |
|
DEFINE_FIELD( CMomentaryRotButton, m_direction, FIELD_INTEGER ), |
|
DEFINE_FIELD( CMomentaryRotButton, m_returnSpeed, FIELD_FLOAT ), |
|
DEFINE_FIELD( CMomentaryRotButton, m_start, FIELD_VECTOR ), |
|
DEFINE_FIELD( CMomentaryRotButton, m_end, FIELD_VECTOR ), |
|
DEFINE_FIELD( CMomentaryRotButton, m_sounds, FIELD_INTEGER ), |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CMomentaryRotButton, CBaseToggle ) |
|
|
|
LINK_ENTITY_TO_CLASS( momentary_rot_button, CMomentaryRotButton ) |
|
|
|
void CMomentaryRotButton::Spawn( void ) |
|
{ |
|
CBaseToggle::AxisDir( pev ); |
|
|
|
if( pev->speed == 0.0f ) |
|
pev->speed = 100.0f; |
|
|
|
if( m_flMoveDistance < 0.0f ) |
|
{ |
|
m_start = pev->angles + pev->movedir * m_flMoveDistance; |
|
m_end = pev->angles; |
|
m_direction = 1; // This will toggle to -1 on the first use() |
|
m_flMoveDistance = -m_flMoveDistance; |
|
} |
|
else |
|
{ |
|
m_start = pev->angles; |
|
m_end = pev->angles + pev->movedir * m_flMoveDistance; |
|
m_direction = -1; // This will toggle to +1 on the first use() |
|
} |
|
|
|
if( pev->spawnflags & SF_MOMENTARY_DOOR ) |
|
pev->solid = SOLID_BSP; |
|
else |
|
pev->solid = SOLID_NOT; |
|
|
|
pev->movetype = MOVETYPE_PUSH; |
|
UTIL_SetOrigin(this, pev->origin); |
|
SET_MODEL( ENT( pev ), STRING( pev->model ) ); |
|
|
|
const char *pszSound = ButtonSound( m_sounds ); |
|
PRECACHE_SOUND( pszSound ); |
|
pev->noise = MAKE_STRING( pszSound ); |
|
m_lastUsed = 0; |
|
} |
|
|
|
void CMomentaryRotButton::KeyValue( KeyValueData *pkvd ) |
|
{ |
|
if( FStrEq( pkvd->szKeyName, "returnspeed" ) ) |
|
{ |
|
m_returnSpeed = atof( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else if( FStrEq( pkvd->szKeyName, "sounds" ) ) |
|
{ |
|
m_sounds = atoi( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else if (FStrEq(pkvd->szKeyName, "axes")) |
|
{ |
|
UTIL_StringToVector((float*)(pev->movedir), pkvd->szValue); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else |
|
CBaseToggle::KeyValue( pkvd ); |
|
} |
|
|
|
void CMomentaryRotButton::PlaySound( void ) |
|
{ |
|
EMIT_SOUND( ENT( pev ), CHAN_VOICE, STRING( pev->noise ), 1, ATTN_NORM ); |
|
} |
|
|
|
// BUGBUG: This design causes a latentcy. When the button is retriggered, the first impulse |
|
// will send the target in the wrong direction because the parameter is calculated based on the |
|
// current, not future position. |
|
void CMomentaryRotButton::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
if (IsLockedByMaster()) return; //LRC |
|
// the distance between the current angle and the "base" angle. |
|
pev->ideal_yaw = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; |
|
|
|
UpdateAllButtons( pev->ideal_yaw, 1 ); |
|
// TODO: which code to use? |
|
#if 1 // spirit |
|
float f = m_fNextThink - pev->ltime; |
|
f = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles + pev->avelocity*f, m_start ) / m_flMoveDistance; |
|
// ALERT(at_console,"sending update = %f\n", f); |
|
UpdateTarget( f ); |
|
#else // hlsdk-xash3d master |
|
// Calculate destination angle and use it to predict value, this prevents sending target in wrong direction on retriggering |
|
Vector dest = pev->angles + pev->avelocity * ( pev->nextthink - pev->ltime ); |
|
float value1 = CBaseToggle::AxisDelta( pev->spawnflags, dest, m_start ) / m_flMoveDistance; |
|
UpdateTarget( value1 ); |
|
#endif |
|
} |
|
|
|
void CMomentaryRotButton::UpdateAllButtons( float value, int start ) |
|
{ |
|
// Update all rot buttons attached to my target |
|
// (this includes myself) |
|
CBaseEntity *pTarget = NULL; |
|
for( ; ; ) |
|
{ |
|
pTarget = UTIL_FindEntityByTarget(pTarget, STRING(pev->target)); |
|
if (FNullEnt(pTarget)) |
|
break; |
|
|
|
if ( FClassnameIs( pTarget->pev, "momentary_rot_button" ) ) |
|
{ |
|
CMomentaryRotButton *pEntity = (CMomentaryRotButton*)pTarget; |
|
if( start ) |
|
pEntity->UpdateSelf( value ); |
|
else |
|
pEntity->UpdateSelfReturn( value ); |
|
} |
|
} |
|
} |
|
|
|
void CMomentaryRotButton::UpdateSelf( float value ) |
|
{ |
|
BOOL fplaysound = FALSE; |
|
|
|
if( !m_lastUsed ) |
|
{ |
|
fplaysound = TRUE; |
|
m_direction = -m_direction; |
|
} |
|
m_lastUsed = 1; |
|
|
|
SetNextThink( 0.1f ); |
|
|
|
//LRC check if we're outside the boundaries |
|
if( m_direction > 0 && value >= 1.0f ) |
|
{ |
|
pev->avelocity = g_vecZero; |
|
pev->angles = m_end; |
|
return; |
|
} |
|
else if( m_direction < 0 && value <= 0.0f ) |
|
{ |
|
pev->avelocity = g_vecZero; |
|
pev->angles = m_start; |
|
return; |
|
} |
|
|
|
if( fplaysound ) |
|
PlaySound(); |
|
|
|
// HACKHACK -- If we're going slow, we'll get multiple player packets per frame; |
|
// bump nexthink on each one to avoid stalling |
|
//LRC- that is to say: our avelocity will get us to the target point in 0.1 secs. |
|
// If we're being told to move further than that, wait that much longer. |
|
if( m_fNextThink < pev->ltime ) |
|
SetNextThink( 0.1f ); |
|
else |
|
{ |
|
AbsoluteNextThink( m_fNextThink + 0.1f ); |
|
} |
|
|
|
pev->avelocity = m_direction * pev->speed * pev->movedir; |
|
SetThink( &CMomentaryRotButton::Off ); |
|
} |
|
|
|
void CMomentaryRotButton::UpdateTarget( float value ) |
|
{ |
|
if( !FStringNull( pev->target ) ) |
|
{ |
|
CBaseEntity* pTarget = NULL; |
|
for( ; ; ) |
|
{ |
|
pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(pev->target)); |
|
if ( !pTarget ) |
|
break; |
|
pTarget->Use( this, this, USE_SET, value ); |
|
} |
|
} |
|
} |
|
|
|
void CMomentaryRotButton::Off( void ) |
|
{ |
|
pev->avelocity = g_vecZero; |
|
m_lastUsed = 0; |
|
if( FBitSet( pev->spawnflags, SF_PENDULUM_AUTO_RETURN ) && m_returnSpeed > 0 ) |
|
{ |
|
SetThink( &CMomentaryRotButton::Return ); |
|
SetNextThink( 0.1f ); |
|
m_direction = -1; |
|
} |
|
else |
|
SetThink( NULL ); |
|
} |
|
|
|
void CMomentaryRotButton::Return( void ) |
|
{ |
|
float value = CBaseToggle::AxisDelta( pev->spawnflags, pev->angles, m_start ) / m_flMoveDistance; |
|
|
|
UpdateAllButtons( value, 0 ); // This will end up calling UpdateSelfReturn() n times, but it still works right |
|
if( value > 0.0f ) |
|
UpdateTarget( value ); |
|
} |
|
|
|
void CMomentaryRotButton::UpdateSelfReturn( float value ) |
|
{ |
|
if( value <= 0.0f ) |
|
{ |
|
pev->avelocity = g_vecZero; |
|
pev->angles = m_start; |
|
DontThink(); |
|
SetThink( NULL ); |
|
} |
|
else |
|
{ |
|
pev->avelocity = -m_returnSpeed * pev->movedir; |
|
SetNextThink( 0.1f ); |
|
} |
|
} |
|
|
|
//---------------------------------------------------------------- |
|
// Spark |
|
//---------------------------------------------------------------- |
|
|
|
class CEnvSpark : public CBaseEntity |
|
{ |
|
public: |
|
void Spawn( void ); |
|
void Precache( void ); |
|
void EXPORT SparkThink( void ); |
|
void EXPORT SparkWait(void); |
|
void EXPORT SparkCyclic(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); |
|
void EXPORT SparkStart( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); |
|
void EXPORT SparkStop( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); |
|
void KeyValue( KeyValueData *pkvd ); |
|
|
|
virtual int Save( CSave &save ); |
|
virtual int Restore( CRestore &restore ); |
|
static TYPEDESCRIPTION m_SaveData[]; |
|
|
|
float m_flDelay; |
|
STATE m_iState; //LRC |
|
virtual STATE GetState( void ) { return m_iState; }; |
|
}; |
|
|
|
TYPEDESCRIPTION CEnvSpark::m_SaveData[] = |
|
{ |
|
DEFINE_FIELD( CEnvSpark, m_flDelay, FIELD_FLOAT), |
|
DEFINE_FIELD( CEnvSpark, m_iState, FIELD_INTEGER), //LRC |
|
}; |
|
|
|
IMPLEMENT_SAVERESTORE( CEnvSpark, CBaseEntity ) |
|
|
|
LINK_ENTITY_TO_CLASS( env_spark, CEnvSpark ) |
|
LINK_ENTITY_TO_CLASS( env_debris, CEnvSpark ) |
|
|
|
void CEnvSpark::Spawn( void ) |
|
{ |
|
SetThink( NULL ); |
|
SetUse( NULL ); |
|
|
|
if (FBitSet(pev->spawnflags, 16)) |
|
{ |
|
SetUse(&CEnvSpark::SparkCyclic); |
|
} |
|
else if (FBitSet(pev->spawnflags, 32)) // Use for on/off |
|
{ |
|
if( FBitSet( pev->spawnflags, 64 ) ) // Start on |
|
{ |
|
SetThink( &CEnvSpark::SparkThink ); // start sparking |
|
SetUse( &CEnvSpark::SparkStop ); // set up +USE to stop sparking |
|
} |
|
else |
|
SetUse( &CEnvSpark::SparkStart ); |
|
} |
|
else |
|
SetThink( &CEnvSpark::SparkThink ); |
|
|
|
if( this->m_pfnThink ) |
|
{ |
|
SetNextThink( 0.1f + RANDOM_FLOAT( 0.0f, 1.5f ) ); |
|
|
|
if( m_flDelay <= 0 ) |
|
m_flDelay = 1.5f; |
|
} |
|
|
|
Precache(); |
|
} |
|
|
|
void CEnvSpark::Precache( void ) |
|
{ |
|
PRECACHE_SOUND( "buttons/spark1.wav" ); |
|
PRECACHE_SOUND( "buttons/spark2.wav" ); |
|
PRECACHE_SOUND( "buttons/spark3.wav" ); |
|
PRECACHE_SOUND( "buttons/spark4.wav" ); |
|
PRECACHE_SOUND( "buttons/spark5.wav" ); |
|
PRECACHE_SOUND( "buttons/spark6.wav" ); |
|
} |
|
|
|
void CEnvSpark::KeyValue( KeyValueData *pkvd ) |
|
{ |
|
if( FStrEq( pkvd->szKeyName, "MaxDelay" ) ) |
|
{ |
|
m_flDelay = atof( pkvd->szValue ); |
|
pkvd->fHandled = TRUE; |
|
} |
|
else if( FStrEq( pkvd->szKeyName, "style" ) || |
|
FStrEq( pkvd->szKeyName, "height" ) || |
|
FStrEq( pkvd->szKeyName, "killtarget" ) || |
|
FStrEq( pkvd->szKeyName, "value1" ) || |
|
FStrEq( pkvd->szKeyName, "value2" ) || |
|
FStrEq( pkvd->szKeyName, "value3" ) ) |
|
pkvd->fHandled = TRUE; |
|
else |
|
CBaseEntity::KeyValue( pkvd ); |
|
} |
|
|
|
void EXPORT CEnvSpark::SparkCyclic(CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
if (m_pfnThink == NULL) |
|
{ |
|
DoSpark( pev, pev->origin ); |
|
SetThink(&CEnvSpark:: SparkWait ); |
|
SetNextThink( m_flDelay ); |
|
} |
|
else |
|
{ |
|
SetThink(&CEnvSpark::SparkThink ); // if we're on SparkWait, change to actually spark at the specified time. |
|
} |
|
} |
|
|
|
void EXPORT CEnvSpark::SparkWait(void) |
|
{ |
|
SetThink( NULL ); |
|
} |
|
|
|
void EXPORT CEnvSpark::SparkThink( void ) |
|
{ |
|
DoSpark( pev, pev->origin ); |
|
if (pev->spawnflags & 16) |
|
{ |
|
SetThink( NULL ); |
|
} |
|
else |
|
{ |
|
SetNextThink( 0.1f + RANDOM_FLOAT (0, m_flDelay) ); |
|
} |
|
} |
|
|
|
void EXPORT CEnvSpark::SparkStart( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
SetUse( &CEnvSpark::SparkStop ); |
|
SetThink( &CEnvSpark::SparkThink ); |
|
m_iState = STATE_ON; //LRC |
|
SetNextThink( 0.1f + RANDOM_FLOAT ( 0, m_flDelay) ); |
|
} |
|
|
|
void EXPORT CEnvSpark::SparkStop( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
SetUse( &CEnvSpark::SparkStart); |
|
SetThink( NULL ); |
|
m_iState = STATE_OFF; //LRC |
|
} |
|
|
|
#define SF_BTARGET_USE 0x0001 |
|
#define SF_BTARGET_ON 0x0002 |
|
|
|
class CButtonTarget : public CBaseEntity |
|
{ |
|
public: |
|
void Spawn( void ); |
|
void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); |
|
int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); |
|
int ObjectCaps( void ); |
|
}; |
|
|
|
LINK_ENTITY_TO_CLASS( button_target, CButtonTarget ) |
|
|
|
void CButtonTarget::Spawn( void ) |
|
{ |
|
pev->movetype = MOVETYPE_PUSH; |
|
pev->solid = SOLID_BSP; |
|
SET_MODEL( ENT( pev ), STRING( pev->model ) ); |
|
pev->takedamage = DAMAGE_YES; |
|
|
|
if( FBitSet( pev->spawnflags, SF_BTARGET_ON ) ) |
|
pev->frame = 1; |
|
} |
|
|
|
void CButtonTarget::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) |
|
{ |
|
if( !ShouldToggle( useType, (int)pev->frame ) ) |
|
return; |
|
pev->frame = 1 - pev->frame; |
|
if( pev->frame ) |
|
SUB_UseTargets( pActivator, USE_ON, 0 ); |
|
else |
|
SUB_UseTargets( pActivator, USE_OFF, 0 ); |
|
} |
|
|
|
int CButtonTarget::ObjectCaps( void ) |
|
{ |
|
int caps = CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; |
|
|
|
if( FBitSet( pev->spawnflags, SF_BTARGET_USE ) ) |
|
return caps | FCAP_IMPULSE_USE; |
|
else |
|
return caps; |
|
} |
|
|
|
int CButtonTarget::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) |
|
{ |
|
Use( Instance( pevAttacker ), this, USE_TOGGLE, 0 ); |
|
|
|
return 1; |
|
}
|
|
|