source-engine/game/server/doors.cpp

1434 lines
41 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: Implements two types of doors: linear and rotating.
//
// $NoKeywords: $
//=============================================================================//
#include "cbase.h"
#include "doors.h"
#include "entitylist.h"
#include "physics.h"
#include "ndebugoverlay.h"
#include "engine/IEngineSound.h"
#include "physics_npc_solver.h"
#ifdef HL1_DLL
#include "filters.h"
#endif
#ifdef CSTRIKE_DLL
#include "KeyValues.h"
#endif
#ifdef TF_DLL
#include "tf_gamerules.h"
#endif // TF_DLL
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
#define CLOSE_AREAPORTAL_THINK_CONTEXT "CloseAreaportalThink"
BEGIN_DATADESC( CBaseDoor )
DEFINE_KEYFIELD( m_vecMoveDir, FIELD_VECTOR, "movedir" ),
DEFINE_FIELD( m_bLockedSentence, FIELD_CHARACTER ),
DEFINE_FIELD( m_bUnlockedSentence, FIELD_CHARACTER ),
DEFINE_KEYFIELD( m_NoiseMoving, FIELD_SOUNDNAME, "noise1" ),
DEFINE_KEYFIELD( m_NoiseArrived, FIELD_SOUNDNAME, "noise2" ),
DEFINE_KEYFIELD( m_NoiseMovingClosed, FIELD_SOUNDNAME, "startclosesound" ),
DEFINE_KEYFIELD( m_NoiseArrivedClosed, FIELD_SOUNDNAME, "closesound" ),
DEFINE_KEYFIELD( m_ChainTarget, FIELD_STRING, "chainstodoor" ),
// DEFINE_FIELD( m_isChaining, FIELD_BOOLEAN ),
// DEFINE_FIELD( m_ls, locksound_t ),
// DEFINE_FIELD( m_isChaining, FIELD_BOOLEAN ),
DEFINE_KEYFIELD( m_ls.sLockedSound, FIELD_SOUNDNAME, "locked_sound" ),
DEFINE_KEYFIELD( m_ls.sUnlockedSound, FIELD_SOUNDNAME, "unlocked_sound" ),
DEFINE_FIELD( m_bLocked, FIELD_BOOLEAN ),
DEFINE_KEYFIELD( m_flWaveHeight, FIELD_FLOAT, "WaveHeight" ),
DEFINE_KEYFIELD( m_flBlockDamage, FIELD_FLOAT, "dmg" ),
DEFINE_KEYFIELD( m_eSpawnPosition, FIELD_INTEGER, "spawnpos" ),
DEFINE_KEYFIELD( m_bForceClosed, FIELD_BOOLEAN, "forceclosed" ),
DEFINE_FIELD( m_bDoorGroup, FIELD_BOOLEAN ),
#ifdef HL1_DLL
DEFINE_KEYFIELD( m_iBlockFilterName, FIELD_STRING, "filtername" ),
DEFINE_FIELD( m_hBlockFilter, FIELD_EHANDLE ),
#endif
DEFINE_KEYFIELD( m_bLoopMoveSound, FIELD_BOOLEAN, "loopmovesound" ),
DEFINE_KEYFIELD( m_bIgnoreDebris, FIELD_BOOLEAN, "ignoredebris" ),
DEFINE_INPUTFUNC( FIELD_VOID, "Open", InputOpen ),
DEFINE_INPUTFUNC( FIELD_VOID, "Close", InputClose ),
DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ),
DEFINE_INPUTFUNC( FIELD_VOID, "Lock", InputLock ),
DEFINE_INPUTFUNC( FIELD_VOID, "Unlock", InputUnlock ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetSpeed", InputSetSpeed ),
DEFINE_INPUTFUNC( FIELD_FLOAT, "SetToggleState", InputSetToggleState ),
DEFINE_OUTPUT( m_OnBlockedOpening, "OnBlockedOpening" ),
DEFINE_OUTPUT( m_OnBlockedClosing, "OnBlockedClosing" ),
DEFINE_OUTPUT( m_OnUnblockedOpening, "OnUnblockedOpening" ),
DEFINE_OUTPUT( m_OnUnblockedClosing, "OnUnblockedClosing" ),
DEFINE_OUTPUT( m_OnFullyClosed, "OnFullyClosed" ),
DEFINE_OUTPUT( m_OnFullyOpen, "OnFullyOpen" ),
DEFINE_OUTPUT( m_OnClose, "OnClose" ),
DEFINE_OUTPUT( m_OnOpen, "OnOpen" ),
DEFINE_OUTPUT( m_OnLockedUse, "OnLockedUse" ),
// Function Pointers
DEFINE_FUNCTION( DoorTouch ),
DEFINE_FUNCTION( DoorGoUp ),
DEFINE_FUNCTION( DoorGoDown ),
DEFINE_FUNCTION( DoorHitTop ),
DEFINE_FUNCTION( DoorHitBottom ),
DEFINE_THINKFUNC( MovingSoundThink ),
DEFINE_THINKFUNC( CloseAreaPortalsThink ),
END_DATADESC()
LINK_ENTITY_TO_CLASS( func_door, CBaseDoor );
//
// func_water is implemented as a linear door so we can raise/lower the water level.
//
LINK_ENTITY_TO_CLASS( func_water, CBaseDoor );
// SendTable stuff.
IMPLEMENT_SERVERCLASS_ST(CBaseDoor, DT_BaseDoor)
SendPropFloat (SENDINFO(m_flWaveHeight), 8, SPROP_ROUNDUP, 0.0f, 8.0f),
END_SEND_TABLE()
#define DOOR_SENTENCEWAIT 6
#define DOOR_SOUNDWAIT 1
#define BUTTON_SOUNDWAIT 0.5
//-----------------------------------------------------------------------------
// Purpose: play door or button locked or unlocked sounds.
// NOTE: this routine is shared by doors and buttons
// Input : pEdict -
// pls -
// flocked - if true, play 'door is locked' sound, otherwise play 'door
// is unlocked' sound.
// fbutton -
//-----------------------------------------------------------------------------
void PlayLockSounds(CBaseEntity *pEdict, locksound_t *pls, int flocked, int fbutton)
{
if ( pEdict->HasSpawnFlags( SF_DOOR_SILENT ) )
{
return;
}
float flsoundwait = ( fbutton ) ? BUTTON_SOUNDWAIT : DOOR_SOUNDWAIT;
if ( flocked )
{
int fplaysound = (pls->sLockedSound != NULL_STRING && gpGlobals->curtime > pls->flwaitSound);
int fplaysentence = (pls->sLockedSentence != NULL_STRING && !pls->bEOFLocked && gpGlobals->curtime > pls->flwaitSentence);
float fvol = ( fplaysound && fplaysentence ) ? 0.25f : 1.0f;
// if there is a locked sound, and we've debounced, play sound
if (fplaysound)
{
// play 'door locked' sound
CPASAttenuationFilter filter( pEdict );
EmitSound_t ep;
ep.m_nChannel = CHAN_ITEM;
ep.m_pSoundName = (char*)STRING(pls->sLockedSound);
ep.m_flVolume = fvol;
ep.m_SoundLevel = SNDLVL_NORM;
CBaseEntity::EmitSound( filter, pEdict->entindex(), ep );
pls->flwaitSound = gpGlobals->curtime + flsoundwait;
}
// if there is a sentence, we've not played all in list, and we've debounced, play sound
if (fplaysentence)
{
// play next 'door locked' sentence in group
int iprev = pls->iLockedSentence;
pls->iLockedSentence = SENTENCEG_PlaySequentialSz( pEdict->edict(),
STRING(pls->sLockedSentence),
0.85f,
SNDLVL_NORM,
0,
100,
pls->iLockedSentence,
FALSE);
pls->iUnlockedSentence = 0;
// make sure we don't keep calling last sentence in list
pls->bEOFLocked = (iprev == pls->iLockedSentence);
pls->flwaitSentence = gpGlobals->curtime + DOOR_SENTENCEWAIT;
}
}
else
{
// UNLOCKED SOUND
int fplaysound = (pls->sUnlockedSound != NULL_STRING && gpGlobals->curtime > pls->flwaitSound);
int fplaysentence = (pls->sUnlockedSentence != NULL_STRING && !pls->bEOFUnlocked && gpGlobals->curtime > pls->flwaitSentence);
float fvol;
// if playing both sentence and sound, lower sound volume so we hear sentence
fvol = ( fplaysound && fplaysentence ) ? 0.25f : 1.0f;
// play 'door unlocked' sound if set
if (fplaysound)
{
CPASAttenuationFilter filter( pEdict );
EmitSound_t ep;
ep.m_nChannel = CHAN_ITEM;
ep.m_pSoundName = (char*)STRING(pls->sUnlockedSound);
ep.m_flVolume = fvol;
ep.m_SoundLevel = SNDLVL_NORM;
CBaseEntity::EmitSound( filter, pEdict->entindex(), ep );
pls->flwaitSound = gpGlobals->curtime + flsoundwait;
}
// play next 'door unlocked' sentence in group
if (fplaysentence)
{
int iprev = pls->iUnlockedSentence;
pls->iUnlockedSentence = SENTENCEG_PlaySequentialSz(pEdict->edict(), STRING(pls->sUnlockedSentence),
0.85, SNDLVL_NORM, 0, 100, pls->iUnlockedSentence, FALSE);
pls->iLockedSentence = 0;
// make sure we don't keep calling last sentence in list
pls->bEOFUnlocked = (iprev == pls->iUnlockedSentence);
pls->flwaitSentence = gpGlobals->curtime + DOOR_SENTENCEWAIT;
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Cache user-entity-field values until spawn is called.
// Input : szKeyName -
// szValue -
// Output : Returns true.
//-----------------------------------------------------------------------------
bool CBaseDoor::KeyValue( const char *szKeyName, const char *szValue )
{
if (FStrEq(szKeyName, "locked_sentence"))
{
m_bLockedSentence = atof(szValue);
}
else if (FStrEq(szKeyName, "unlocked_sentence"))
{
m_bUnlockedSentence = atof(szValue);
}
else
return BaseClass::KeyValue( szKeyName, szValue );
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseDoor::Spawn()
{
Precache();
#ifdef HL1_DLL
SetSolid( SOLID_BSP );
#else
if ( GetMoveParent() && GetRootMoveParent()->GetSolid() == SOLID_BSP )
{
SetSolid( SOLID_BSP );
}
else
{
SetSolid( SOLID_VPHYSICS );
}
#endif
// Convert movedir from angles to a vector
QAngle angMoveDir = QAngle( m_vecMoveDir.x, m_vecMoveDir.y, m_vecMoveDir.z );
AngleVectors( angMoveDir, &m_vecMoveDir );
SetModel( STRING( GetModelName() ) );
m_vecPosition1 = GetLocalOrigin();
// Subtract 2 from size because the engine expands bboxes by 1 in all directions making the size too big
Vector vecOBB = CollisionProp()->OBBSize();
vecOBB -= Vector( 2, 2, 2 );
m_vecPosition2 = m_vecPosition1 + (m_vecMoveDir * (DotProductAbs( m_vecMoveDir, vecOBB ) - m_flLip));
if ( !IsRotatingDoor() )
{
if ( ( m_eSpawnPosition == FUNC_DOOR_SPAWN_OPEN ) || HasSpawnFlags( SF_DOOR_START_OPEN_OBSOLETE ) )
{ // swap pos1 and pos2, put door at pos2
UTIL_SetOrigin( this, m_vecPosition2);
m_toggle_state = TS_AT_TOP;
}
else
{
m_toggle_state = TS_AT_BOTTOM;
}
}
if (HasSpawnFlags(SF_DOOR_LOCKED))
{
m_bLocked = true;
}
SetMoveType( MOVETYPE_PUSH );
if (m_flSpeed == 0)
{
m_flSpeed = 100;
}
SetTouch( &CBaseDoor::DoorTouch );
if ( !FClassnameIs( this, "func_water" ) )
{
if ( HasSpawnFlags(SF_DOOR_PASSABLE) )
{
//normal door
AddEFlags( EFL_USE_PARTITION_WHEN_NOT_SOLID );
AddSolidFlags( FSOLID_NOT_SOLID );
}
if ( HasSpawnFlags( SF_DOOR_NONSOLID_TO_PLAYER ) )
{
SetCollisionGroup( COLLISION_GROUP_PASSABLE_DOOR );
// HACKHACK: Set this hoping that any children of the door that get blocked by the player
// will get fixed up by vphysics
// NOTE: We could decouple this as a separate behavior, but managing player collisions is already complex enough.
// NOTE: This is necessary to prevent the player from blocking the wrecked train car in ep2_outland_01
AddFlag( FL_UNBLOCKABLE_BY_PLAYER );
}
if ( m_bIgnoreDebris )
{
// both of these flags want to set the collision group and
// there isn't a combo group
Assert( !HasSpawnFlags( SF_DOOR_NONSOLID_TO_PLAYER ) );
if ( HasSpawnFlags( SF_DOOR_NONSOLID_TO_PLAYER ) )
{
Warning("Door %s with conflicting collision settings, removing ignoredebris\n", GetDebugName() );
}
else
{
SetCollisionGroup( COLLISION_GROUP_INTERACTIVE );
}
}
}
if ( ( m_eSpawnPosition == FUNC_DOOR_SPAWN_OPEN ) && HasSpawnFlags( SF_DOOR_START_OPEN_OBSOLETE ) )
{
Warning("Door %s using obsolete 'Start Open' spawnflag with 'Spawn Position' set to 'Open'. Reverting to old behavior.\n", GetDebugName() );
}
CreateVPhysics();
#ifdef TF_DLL
if ( TFGameRules() && TFGameRules()->IsMultiplayer() )
{
// Never block doors in TF2 - to prevent various exploits.
m_bIgnoreNonPlayerEntsOnBlock = true;
}
#else
m_bIgnoreNonPlayerEntsOnBlock = false;
#endif // TF_DLL
}
void CBaseDoor::MovingSoundThink( void )
{
CPASAttenuationFilter filter( this );
filter.MakeReliable();
EmitSound_t ep;
ep.m_nChannel = CHAN_STATIC;
if ( m_NoiseMovingClosed == NULL_STRING || m_toggle_state == TS_GOING_DOWN || m_toggle_state == TS_AT_BOTTOM )
{
ep.m_pSoundName = (char*)STRING(m_NoiseMoving);
}
else
{
ep.m_pSoundName = (char*)STRING(m_NoiseMovingClosed);
}
ep.m_flVolume = 1;
ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, entindex(), ep );
//Only loop sounds in HL1 to maintain HL2 behavior
if( ShouldLoopMoveSound() )
{
float duration = enginesound->GetSoundDuration( ep.m_pSoundName );
SetContextThink( &CBaseDoor::MovingSoundThink, gpGlobals->curtime + duration, "MovingSound" );
}
}
void CBaseDoor::StartMovingSound( void )
{
MovingSoundThink();
#ifdef CSTRIKE_DLL // this event is only used by CS:S bots
CBasePlayer *player = ToBasePlayer(m_hActivator);
IGameEvent * event = gameeventmanager->CreateEvent( "door_moving" );
if( event )
{
event->SetInt( "entindex", entindex() );
event->SetInt( "userid", (player)?player->GetUserID():0 );
gameeventmanager->FireEvent( event );
}
#endif
}
void CBaseDoor::StopMovingSound(void)
{
SetContextThink( NULL, gpGlobals->curtime, "MovingSound" );
char *pSoundName;
if ( m_NoiseMovingClosed == NULL_STRING || m_toggle_state == TS_GOING_UP || m_toggle_state == TS_AT_TOP )
{
pSoundName = (char*)STRING(m_NoiseMoving);
}
else
{
pSoundName = (char*)STRING(m_NoiseMovingClosed);
}
StopSound( entindex(), CHAN_STATIC, pSoundName );
}
bool CBaseDoor::ShouldSavePhysics()
{
// don't save physics if you're func_water
return !FClassnameIs( this, "func_water" );
}
//-----------------------------------------------------------------------------
bool CBaseDoor::CreateVPhysics( )
{
if ( !FClassnameIs( this, "func_water" ) )
{
//normal door
// NOTE: Create this even when the door is not solid to support constraints.
VPhysicsInitShadow( false, false );
}
else
{
// special contents
AddSolidFlags( FSOLID_VOLUME_CONTENTS );
SETBITS( m_spawnflags, SF_DOOR_SILENT ); // water is silent for now
IPhysicsObject *pPhysics = VPhysicsInitShadow( false, false );
fluidparams_t fluid;
Assert( CollisionProp()->GetCollisionAngles() == vec3_angle );
fluid.damping = 0.01f;
fluid.surfacePlane[0] = 0;
fluid.surfacePlane[1] = 0;
fluid.surfacePlane[2] = 1;
fluid.surfacePlane[3] = CollisionProp()->GetCollisionOrigin().z + CollisionProp()->OBBMaxs().z - 1;
fluid.currentVelocity.Init(0,0,0);
fluid.torqueFactor = 0.1f;
fluid.viscosityFactor = 0.01f;
fluid.pGameData = static_cast<void *>(this);
//FIXME: Currently there's no way to specify that you want slime
fluid.contents = CONTENTS_WATER;
physenv->CreateFluidController( pPhysics, &fluid );
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseDoor::Activate( void )
{
BaseClass::Activate();
CBaseDoor *pDoorList[64];
m_bDoorGroup = true;
// force movement groups to sync!!!
int doorCount = GetDoorMovementGroup( pDoorList, ARRAYSIZE(pDoorList) );
for ( int i = 0; i < doorCount; i++ )
{
if ( pDoorList[i]->m_vecMoveDir == m_vecMoveDir )
{
bool error = false;
if ( pDoorList[i]->IsRotatingDoor() )
{
error = ( pDoorList[i]->GetLocalAngles() != GetLocalAngles() ) ? true : false;
}
else
{
error = ( pDoorList[i]->GetLocalOrigin() != GetLocalOrigin() ) ? true : false;
}
if ( error )
{
// don't do group blocking
m_bDoorGroup = false;
#ifdef HL1_DLL
// UNDONE: This should probably fixup m_vecPosition1 & m_vecPosition2
Warning("Door group %s has misaligned origin!\n", STRING(GetEntityName()) );
#endif
}
}
}
switch ( m_toggle_state )
{
case TS_AT_TOP:
UpdateAreaPortals( true );
break;
case TS_AT_BOTTOM:
UpdateAreaPortals( false );
break;
}
#ifdef HL1_DLL
// Get a handle to my filter entity if there is one
if (m_iBlockFilterName != NULL_STRING)
{
m_hBlockFilter = dynamic_cast<CBaseFilter *>(gEntList.FindEntityByName( NULL, m_iBlockFilterName, NULL ));
}
#endif
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : state -
//-----------------------------------------------------------------------------
// This is ONLY used by the node graph to test movement through a door
void CBaseDoor::InputSetToggleState( inputdata_t &inputdata )
{
SetToggleState( inputdata.value.Int() );
}
void CBaseDoor::SetToggleState( int state )
{
if ( state == TS_AT_TOP )
UTIL_SetOrigin( this, m_vecPosition2 );
else
UTIL_SetOrigin( this, m_vecPosition1 );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseDoor::Precache( void )
{
//Fill in a default value if necessary
if ( IsRotatingDoor() )
{
UTIL_ValidateSoundName( m_NoiseMoving, "RotDoorSound.DefaultMove" );
UTIL_ValidateSoundName( m_NoiseArrived, "RotDoorSound.DefaultArrive" );
UTIL_ValidateSoundName( m_ls.sLockedSound, "RotDoorSound.DefaultLocked" );
UTIL_ValidateSoundName( m_ls.sUnlockedSound,"DoorSound.Null" );
}
else
{
UTIL_ValidateSoundName( m_NoiseMoving, "DoorSound.DefaultMove" );
UTIL_ValidateSoundName( m_NoiseArrived, "DoorSound.DefaultArrive" );
#ifndef HL1_DLL
UTIL_ValidateSoundName( m_ls.sLockedSound, "DoorSound.DefaultLocked" );
#endif
UTIL_ValidateSoundName( m_ls.sUnlockedSound,"DoorSound.Null" );
}
#ifdef HL1_DLL
if( m_ls.sLockedSound != NULL_STRING && strlen((char*)STRING(m_ls.sLockedSound)) < 4 )
{
// Too short to be ANYTHING ".wav", so it must be an old index into a long-lost
// array of sound choices. slam it to a known "deny" sound. We lose the designer's
// original selection, but we don't get unresponsive doors.
m_ls.sLockedSound = AllocPooledString("buttons/button2.wav");
}
#endif//HL1_DLL
//Precache them all
PrecacheScriptSound( (char *) STRING(m_NoiseMoving) );
PrecacheScriptSound( (char *) STRING(m_NoiseArrived) );
PrecacheScriptSound( (char *) STRING(m_NoiseMovingClosed) );
PrecacheScriptSound( (char *) STRING(m_NoiseArrivedClosed) );
PrecacheScriptSound( (char *) STRING(m_ls.sLockedSound) );
PrecacheScriptSound( (char *) STRING(m_ls.sUnlockedSound) );
//Get sentence group names, for doors which are directly 'touched' to open
switch (m_bLockedSentence)
{
case 1: m_ls.sLockedSentence = AllocPooledString("NA"); break; // access denied
case 2: m_ls.sLockedSentence = AllocPooledString("ND"); break; // security lockout
case 3: m_ls.sLockedSentence = AllocPooledString("NF"); break; // blast door
case 4: m_ls.sLockedSentence = AllocPooledString("NFIRE"); break; // fire door
case 5: m_ls.sLockedSentence = AllocPooledString("NCHEM"); break; // chemical door
case 6: m_ls.sLockedSentence = AllocPooledString("NRAD"); break; // radiation door
case 7: m_ls.sLockedSentence = AllocPooledString("NCON"); break; // gen containment
case 8: m_ls.sLockedSentence = AllocPooledString("NH"); break; // maintenance door
case 9: m_ls.sLockedSentence = AllocPooledString("NG"); break; // broken door
default: m_ls.sLockedSentence = NULL_STRING; break;
}
switch (m_bUnlockedSentence)
{
case 1: m_ls.sUnlockedSentence = AllocPooledString("EA"); break; // access granted
case 2: m_ls.sUnlockedSentence = AllocPooledString("ED"); break; // security door
case 3: m_ls.sUnlockedSentence = AllocPooledString("EF"); break; // blast door
case 4: m_ls.sUnlockedSentence = AllocPooledString("EFIRE"); break; // fire door
case 5: m_ls.sUnlockedSentence = AllocPooledString("ECHEM"); break; // chemical door
case 6: m_ls.sUnlockedSentence = AllocPooledString("ERAD"); break; // radiation door
case 7: m_ls.sUnlockedSentence = AllocPooledString("ECON"); break; // gen containment
case 8: m_ls.sUnlockedSentence = AllocPooledString("EH"); break; // maintenance door
default: m_ls.sUnlockedSentence = NULL_STRING; break;
}
}
//-----------------------------------------------------------------------------
// Purpose: Doors not tied to anything (e.g. button, another door) can be touched,
// to make them activate.
// Input : *pOther -
//-----------------------------------------------------------------------------
void CBaseDoor::DoorTouch( CBaseEntity *pOther )
{
if( m_ChainTarget != NULL_STRING )
ChainTouch( pOther );
// Ignore touches by anything but players.
if ( !pOther->IsPlayer() )
{
#ifdef HL1_DLL
if( PassesBlockTouchFilter( pOther ) && m_toggle_state == TS_GOING_DOWN )
{
DoorGoUp();
}
#endif
return;
}
// If door is not opened by touch, do nothing.
if ( !HasSpawnFlags(SF_DOOR_PTOUCH) )
{
#ifdef HL1_DLL
if( m_toggle_state == TS_AT_BOTTOM )
{
PlayLockSounds(this, &m_ls, TRUE, FALSE);
}
#endif//HL1_DLL
return;
}
// If door has master, and it's not ready to trigger,
// play 'locked' sound.
if (m_sMaster != NULL_STRING && !UTIL_IsMasterTriggered(m_sMaster, pOther))
{
PlayLockSounds(this, &m_ls, TRUE, FALSE);
}
if (m_bLocked)
{
m_OnLockedUse.FireOutput( pOther, pOther );
PlayLockSounds(this, &m_ls, TRUE, FALSE);
return;
}
// Remember who activated the door.
m_hActivator = pOther;
if (DoorActivate( ))
{
// Temporarily disable the touch function, until movement is finished.
SetTouch( NULL );
}
}
#ifdef HL1_DLL
bool CBaseDoor::PassesBlockTouchFilter(CBaseEntity *pOther)
{
CBaseFilter* pFilter = (CBaseFilter*)(m_hBlockFilter.Get());
return ( pFilter && pFilter->PassesFilter( this, pOther ) );
}
#endif
//-----------------------------------------------------------------------------
// Purpose: Delays turning off area portals when closing doors to prevent visual artifacts
//-----------------------------------------------------------------------------
void CBaseDoor::CloseAreaPortalsThink( void )
{
UpdateAreaPortals( false );
SetContextThink( NULL, gpGlobals->curtime, CLOSE_AREAPORTAL_THINK_CONTEXT );
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : isOpen -
//-----------------------------------------------------------------------------
void CBaseDoor::UpdateAreaPortals( bool isOpen )
{
// cancel pending close
SetContextThink( NULL, gpGlobals->curtime, CLOSE_AREAPORTAL_THINK_CONTEXT );
if ( IsRotatingDoor() && HasSpawnFlags(SF_DOOR_START_OPEN_OBSOLETE) ) // logic inverted when using rot doors that start open
isOpen = !isOpen;
string_t name = GetEntityName();
if ( !name )
return;
CBaseEntity *pPortal = NULL;
while ( ( pPortal = gEntList.FindEntityByClassname( pPortal, "func_areaportal" ) ) != NULL )
{
if ( pPortal->HasTarget( name ) )
{
// USE_ON means open the portal, off means close it
pPortal->Use( this, this, isOpen?USE_ON:USE_OFF, 0 );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Called when the player uses the door.
// Input : pActivator -
// pCaller -
// useType -
// value -
//-----------------------------------------------------------------------------
void CBaseDoor::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value )
{
m_hActivator = pActivator;
if( m_ChainTarget != NULL_STRING )
ChainUse();
// We can't +use this if it can't be +used
if ( m_hActivator != NULL && m_hActivator->IsPlayer() && HasSpawnFlags( SF_DOOR_PUSE ) == false )
{
PlayLockSounds( this, &m_ls, TRUE, FALSE );
return;
}
bool bAllowUse = false;
// if not ready to be used, ignore "use" command.
if( HasSpawnFlags(SF_DOOR_NEW_USE_RULES) )
{
//New behavior:
// If not ready to be used, ignore "use" command.
// Allow use in these cases:
// - when the door is closed/closing
// - when the door is open/opening and can be manually closed
if ( ( m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN ) || ( HasSpawnFlags(SF_DOOR_NO_AUTO_RETURN) && ( m_toggle_state == TS_AT_TOP || m_toggle_state == TS_GOING_UP ) ) )
bAllowUse = true;
}
else
{
// Legacy behavior:
if (m_toggle_state == TS_AT_BOTTOM || (HasSpawnFlags(SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP) )
bAllowUse = true;
}
if( bAllowUse )
{
if (m_bLocked)
{
m_OnLockedUse.FireOutput( pActivator, pCaller );
PlayLockSounds(this, &m_ls, TRUE, FALSE);
}
else
{
DoorActivate();
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Passes Use along to certain named doors.
//-----------------------------------------------------------------------------
void CBaseDoor::ChainUse( void )
{
if ( m_isChaining )
return;
CBaseEntity *ent = NULL;
while ( ( ent = gEntList.FindEntityByName( ent, m_ChainTarget, NULL ) ) != NULL )
{
if ( ent == this )
continue;
CBaseDoor *door = dynamic_cast< CBaseDoor * >( ent );
if ( door )
{
door->SetChaining( true );
door->Use( m_hActivator, NULL, USE_TOGGLE, 0.0f ); // only the first param is used
door->SetChaining( false );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Passes Touch along to certain named doors.
//-----------------------------------------------------------------------------
void CBaseDoor::ChainTouch( CBaseEntity *pOther )
{
if ( m_isChaining )
return;
CBaseEntity *ent = NULL;
while ( ( ent = gEntList.FindEntityByName( ent, m_ChainTarget, NULL ) ) != NULL )
{
if ( ent == this )
continue;
CBaseDoor *door = dynamic_cast< CBaseDoor * >( ent );
if ( door )
{
door->SetChaining( true );
door->Touch( pOther );
door->SetChaining( false );
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Closes the door if it is not already closed.
//-----------------------------------------------------------------------------
void CBaseDoor::InputClose( inputdata_t &inputdata )
{
if ( m_toggle_state != TS_AT_BOTTOM )
{
DoorGoDown();
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler that locks the door.
//-----------------------------------------------------------------------------
void CBaseDoor::InputLock( inputdata_t &inputdata )
{
Lock();
}
//-----------------------------------------------------------------------------
// Purpose: Opens the door if it is not already open.
//-----------------------------------------------------------------------------
void CBaseDoor::InputOpen( inputdata_t &inputdata )
{
if (m_toggle_state != TS_AT_TOP && m_toggle_state != TS_GOING_UP )
{
// I'm locked, can't open
if (m_bLocked)
return;
// Play door unlock sounds.
PlayLockSounds(this, &m_ls, false, false);
DoorGoUp();
}
}
//-----------------------------------------------------------------------------
// Purpose: Opens the door if it is not already open.
//-----------------------------------------------------------------------------
void CBaseDoor::InputToggle( inputdata_t &inputdata )
{
// I'm locked, can't open
if (m_bLocked)
return;
if (m_toggle_state == TS_AT_BOTTOM)
{
DoorGoUp();
}
else if (m_toggle_state == TS_AT_TOP)
{
DoorGoDown();
}
}
//-----------------------------------------------------------------------------
// Purpose: Input handler that unlocks the door.
//-----------------------------------------------------------------------------
void CBaseDoor::InputUnlock( inputdata_t &inputdata )
{
Unlock();
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CBaseDoor::InputSetSpeed( inputdata_t &inputdata )
{
m_flSpeed = inputdata.value.Float();
}
//-----------------------------------------------------------------------------
// Purpose: Locks the door so that it cannot be opened.
//-----------------------------------------------------------------------------
void CBaseDoor::Lock( void )
{
m_bLocked = true;
}
//-----------------------------------------------------------------------------
// Purpose: Unlocks the door so that it can be opened.
//-----------------------------------------------------------------------------
void CBaseDoor::Unlock( void )
{
m_bLocked = false;
}
//-----------------------------------------------------------------------------
// Purpose: Causes the door to "do its thing", i.e. start moving, and cascade activation.
// Output : int
//-----------------------------------------------------------------------------
int CBaseDoor::DoorActivate( )
{
if (!UTIL_IsMasterTriggered(m_sMaster, m_hActivator))
return 0;
if (HasSpawnFlags(SF_DOOR_NO_AUTO_RETURN) && m_toggle_state == TS_AT_TOP)
{
// door should close
DoorGoDown();
}
else
{
// door should open
// play door unlock sounds
PlayLockSounds(this, &m_ls, FALSE, FALSE);
if ( m_toggle_state != TS_AT_TOP && m_toggle_state != TS_GOING_UP )
{
DoorGoUp();
}
}
return 1;
}
//-----------------------------------------------------------------------------
// Purpose: Starts the door going to its "up" position (simply ToggleData->vecPosition2).
//-----------------------------------------------------------------------------
void CBaseDoor::DoorGoUp( void )
{
edict_t *pevActivator;
UpdateAreaPortals( true );
// It could be going-down, if blocked.
ASSERT(m_toggle_state == TS_AT_BOTTOM || m_toggle_state == TS_GOING_DOWN);
// emit door moving and stop sounds on CHAN_STATIC so that the multicast doesn't
// filter them out and leave a client stuck with looping door sounds!
if ( !HasSpawnFlags(SF_DOOR_SILENT ) )
{
// If we're not moving already, start the moving noise
if ( m_toggle_state != TS_GOING_UP && m_toggle_state != TS_GOING_DOWN )
{
StartMovingSound();
}
}
m_toggle_state = TS_GOING_UP;
SetMoveDone( &CBaseDoor::DoorHitTop );
if ( IsRotatingDoor() ) // !!! BUGBUG Triggered doors don't work with this yet
{
float sign = 1.0;
if ( m_hActivator != NULL )
{
pevActivator = m_hActivator->edict();
if ( !HasSpawnFlags( SF_DOOR_ONEWAY ) && m_vecMoveAng.y ) // Y axis rotation, move away from the player
{
// Positive is CCW, negative is CW, so make 'sign' 1 or -1 based on which way we want to open.
// Important note: All doors face East at all times, and twist their local angle to open.
// So you can't look at the door's facing to determine which way to open.
Vector nearestPoint;
CollisionProp()->CalcNearestPoint( m_hActivator->GetAbsOrigin(), &nearestPoint );
Vector activatorToNearestPoint = nearestPoint - m_hActivator->GetAbsOrigin();
activatorToNearestPoint.z = 0;
Vector activatorToOrigin = GetAbsOrigin() - m_hActivator->GetAbsOrigin();
activatorToOrigin.z = 0;
// Point right hand at door hinge, curl hand towards closest spot on door, if thumb
// is up, open door CW. -- Department of Basic Cross Product Understanding for Noobs
Vector cross = activatorToOrigin.Cross( activatorToNearestPoint );
if( cross.z > 0.0f )
{
sign = -1.0f;
}
}
}
AngularMove(m_vecAngle2*sign, m_flSpeed);
}
else
{
LinearMove(m_vecPosition2, m_flSpeed);
}
//Fire our open ouput
m_OnOpen.FireOutput( this, this );
}
//-----------------------------------------------------------------------------
// Purpose: The door has reached the "up" position. Either go back down, or
// wait for another activation.
//-----------------------------------------------------------------------------
void CBaseDoor::DoorHitTop( void )
{
if ( !HasSpawnFlags( SF_DOOR_SILENT ) )
{
CPASAttenuationFilter filter( this );
filter.MakeReliable();
StopMovingSound();
EmitSound_t ep;
ep.m_nChannel = CHAN_STATIC;
ep.m_pSoundName = (char*)STRING(m_NoiseArrived);
ep.m_flVolume = 1;
ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, entindex(), ep );
}
ASSERT(m_toggle_state == TS_GOING_UP);
m_toggle_state = TS_AT_TOP;
// toggle-doors don't come down automatically, they wait for refire.
if (HasSpawnFlags( SF_DOOR_NO_AUTO_RETURN))
{
// Re-instate touch method, movement is complete
SetTouch( &CBaseDoor::DoorTouch );
}
else
{
// In flWait seconds, DoorGoDown will fire, unless wait is -1, then door stays open
SetMoveDoneTime( m_flWait );
SetMoveDone( &CBaseDoor::DoorGoDown );
if ( m_flWait == -1 )
{
SetNextThink( TICK_NEVER_THINK );
}
}
if (HasSpawnFlags(SF_DOOR_START_OPEN_OBSOLETE) )
{
m_OnFullyClosed.FireOutput(this, this);
}
else
{
m_OnFullyOpen.FireOutput(this, this);
}
}
//-----------------------------------------------------------------------------
// Purpose: Starts the door going to its "down" position (simply ToggleData->vecPosition1).
//-----------------------------------------------------------------------------
void CBaseDoor::DoorGoDown( void )
{
if ( !HasSpawnFlags( SF_DOOR_SILENT ) )
{
// If we're not moving already, start the moving noise
if ( m_toggle_state != TS_GOING_UP && m_toggle_state != TS_GOING_DOWN )
{
StartMovingSound();
}
}
#ifdef DOOR_ASSERT
ASSERT(m_toggle_state == TS_AT_TOP);
#endif // DOOR_ASSERT
m_toggle_state = TS_GOING_DOWN;
SetMoveDone( &CBaseDoor::DoorHitBottom );
if ( IsRotatingDoor() )//rotating door
AngularMove( m_vecAngle1, m_flSpeed);
else
LinearMove( m_vecPosition1, m_flSpeed);
//Fire our closed output
m_OnClose.FireOutput( this, this );
}
//-----------------------------------------------------------------------------
// Purpose: The door has reached the "down" position. Back to quiescence.
//-----------------------------------------------------------------------------
void CBaseDoor::DoorHitBottom( void )
{
if ( !HasSpawnFlags( SF_DOOR_SILENT ) )
{
CPASAttenuationFilter filter( this );
filter.MakeReliable();
StopMovingSound();
EmitSound_t ep;
ep.m_nChannel = CHAN_STATIC;
if ( m_NoiseArrivedClosed == NULL_STRING )
ep.m_pSoundName = (char*)STRING(m_NoiseArrived);
else
ep.m_pSoundName = (char*)STRING(m_NoiseArrivedClosed);
ep.m_flVolume = 1;
ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, entindex(), ep );
}
ASSERT(m_toggle_state == TS_GOING_DOWN);
m_toggle_state = TS_AT_BOTTOM;
// Re-instate touch method, cycle is complete
SetTouch( &CBaseDoor::DoorTouch );
if (HasSpawnFlags(SF_DOOR_START_OPEN_OBSOLETE))
{
m_OnFullyOpen.FireOutput(m_hActivator, this);
}
else
{
m_OnFullyClosed.FireOutput(m_hActivator, this);
}
// Close the area portals just after the door closes, to prevent visual artifacts in multiplayer games
SetContextThink( &CBaseDoor::CloseAreaPortalsThink, gpGlobals->curtime + 0.5f, CLOSE_AREAPORTAL_THINK_CONTEXT );
}
// Lists all doors in the same movement group as this one
int CBaseDoor::GetDoorMovementGroup( CBaseDoor *pDoorList[], int listMax )
{
int count = 0;
CBaseEntity *pTarget = NULL;
// Block all door pieces with the same targetname here.
if ( GetEntityName() != NULL_STRING )
{
for (;;)
{
pTarget = gEntList.FindEntityByName( pTarget, GetEntityName(), NULL );
if ( pTarget != this )
{
if ( !pTarget )
break;
CBaseDoor *pDoor = dynamic_cast<CBaseDoor *>(pTarget);
if ( pDoor && count < listMax )
{
pDoorList[count] = pDoor;
count++;
}
}
}
}
return count;
}
//-----------------------------------------------------------------------------
// Purpose: Called the first frame that the door is blocked while opening or closing.
// Input : pOther - The blocking entity.
//-----------------------------------------------------------------------------
void CBaseDoor::StartBlocked( CBaseEntity *pOther )
{
//
// Fire whatever events we need to due to our blocked state.
//
if (m_toggle_state == TS_GOING_DOWN)
{
m_OnBlockedClosing.FireOutput(pOther, this);
}
else
{
m_OnBlockedOpening.FireOutput(pOther, this);
}
}
//-----------------------------------------------------------------------------
// Purpose: Called every frame when the door is blocked while opening or closing.
// Input : pOther - The blocking entity.
//-----------------------------------------------------------------------------
void CBaseDoor::Blocked( CBaseEntity *pOther )
{
// Hurt the blocker a little.
if ( m_flBlockDamage )
{
// if the door is marked "force closed" or it has a negative wait, then there's nothing to do but
// push/damage the object.
// If block damage is set, but this object is a physics prop that can't be damaged, just
// give up and disable collisions
if ( (m_bForceClosed || m_flWait < 0) && pOther->GetMoveType() == MOVETYPE_VPHYSICS &&
(pOther->m_takedamage == DAMAGE_NO || pOther->m_takedamage == DAMAGE_EVENTS_ONLY) )
{
EntityPhysics_CreateSolver( this, pOther, true, 4.0f );
}
else
{
pOther->TakeDamage( CTakeDamageInfo( this, this, m_flBlockDamage, DMG_CRUSH ) );
}
}
// If set, ignore non-player ents that block us. Mainly of use in multiplayer to prevent exploits.
else if ( pOther && !pOther->IsPlayer() && m_bIgnoreNonPlayerEntsOnBlock )
{
return;
}
// If we're set to force ourselves closed, keep going
if ( m_bForceClosed )
return;
// if a door has a negative wait, it would never come back if blocked,
// so let it just squash the object to death real fast
if (m_flWait >= 0)
{
if (m_toggle_state == TS_GOING_DOWN)
{
DoorGoUp();
}
else
{
DoorGoDown();
}
}
// Block all door pieces with the same targetname here.
if ( GetEntityName() != NULL_STRING )
{
CBaseDoor *pDoorList[64];
int doorCount = GetDoorMovementGroup( pDoorList, ARRAYSIZE(pDoorList) );
for ( int i = 0; i < doorCount; i++ )
{
CBaseDoor *pDoor = pDoorList[i];
if ( pDoor->m_flWait >= 0)
{
if (m_bDoorGroup && pDoor->m_vecMoveDir == m_vecMoveDir && pDoor->GetAbsVelocity() == GetAbsVelocity() && pDoor->GetLocalAngularVelocity() == GetLocalAngularVelocity())
{
pDoor->m_nSimulationTick = m_nSimulationTick; // don't run simulation this frame if you haven't run yet
// this is the most hacked, evil, bastardized thing I've ever seen. kjb
if ( !pDoor->IsRotatingDoor() )
{// set origin to realign normal doors
pDoor->SetLocalOrigin( GetLocalOrigin() );
pDoor->SetAbsVelocity( vec3_origin );// stop!
}
else
{// set angles to realign rotating doors
pDoor->SetLocalAngles( GetLocalAngles() );
pDoor->SetLocalAngularVelocity( vec3_angle );
}
}
if ( pDoor->m_toggle_state == TS_GOING_DOWN)
pDoor->DoorGoUp();
else
pDoor->DoorGoDown();
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose: Called the first frame that the door is unblocked while opening or closing.
//-----------------------------------------------------------------------------
void CBaseDoor::EndBlocked( void )
{
//
// Fire whatever events we need to due to our unblocked state.
//
if (m_toggle_state == TS_GOING_DOWN)
{
m_OnUnblockedClosing.FireOutput(this, this);
}
else
{
m_OnUnblockedOpening.FireOutput(this, this);
}
}
/*func_door_rotating
TOGGLE causes the door to wait in both the start and end states for
a trigger event.
START_OPEN causes the door to move to its destination when spawned,
and operate in reverse. It is used to temporarily or permanently
close off an area when triggered (not usefull for touch or
takedamage doors).
You need to have an origin brush as part of this entity. The
center of that brush will be
the point around which it is rotated. It will rotate around the Z
axis by default. You can
check either the X_AXIS or Y_AXIS box to change that.
"distance" is how many degrees the door will be rotated.
"speed" determines how fast the door moves; default value is 100.
REVERSE will cause the door to rotate in the opposite direction.
"angle" determines the opening direction
"targetname" if set, no touch field will be spawned and a remote
button or trigger field activates the door.
"health" if set, door must be shot open
"speed" movement speed (100 default)
"wait" wait before returning (3 default, -1 = never return)
"dmg" damage to inflict when blocked (2 default)
*/
//==================================================
// CRotDoor
//==================================================
class CRotDoor : public CBaseDoor
{
public:
DECLARE_CLASS( CRotDoor, CBaseDoor );
void Spawn( void );
bool CreateVPhysics();
// This is ONLY used by the node graph to test movement through a door
virtual void SetToggleState( int state );
virtual bool IsRotatingDoor() { return true; }
bool m_bSolidBsp;
DECLARE_DATADESC();
};
LINK_ENTITY_TO_CLASS( func_door_rotating, CRotDoor );
BEGIN_DATADESC( CRotDoor )
DEFINE_KEYFIELD( m_bSolidBsp, FIELD_BOOLEAN, "solidbsp" ),
END_DATADESC()
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CRotDoor::Spawn( void )
{
BaseClass::Spawn();
// set the axis of rotation
CBaseToggle::AxisDir();
// check for clockwise rotation
if ( HasSpawnFlags(SF_DOOR_ROTATE_BACKWARDS) )
m_vecMoveAng = m_vecMoveAng * -1;
//m_flWait = 2; who the hell did this? (sjb)
m_vecAngle1 = GetLocalAngles();
m_vecAngle2 = GetLocalAngles() + m_vecMoveAng * m_flMoveDistance;
ASSERTSZ(m_vecAngle1 != m_vecAngle2, "rotating door start/end positions are equal\n");
// Starting open allows a func_door to be lighted in the closed position but
// spawn in the open position
//
// SF_DOOR_START_OPEN_OBSOLETE is an old broken way of spawning open that has
// been deprecated.
if ( HasSpawnFlags(SF_DOOR_START_OPEN_OBSOLETE) )
{
// swap pos1 and pos2, put door at pos2, invert movement direction
QAngle vecNewAngles = m_vecAngle2;
m_vecAngle2 = m_vecAngle1;
m_vecAngle1 = vecNewAngles;
m_vecMoveAng = -m_vecMoveAng;
// We've already had our physics setup in BaseClass::Spawn, so teleport to our
// current position. If we don't do this, our vphysics shadow will not update.
Teleport( NULL, &m_vecAngle1, NULL );
m_toggle_state = TS_AT_BOTTOM;
}
else if ( m_eSpawnPosition == FUNC_DOOR_SPAWN_OPEN )
{
// We've already had our physics setup in BaseClass::Spawn, so teleport to our
// current position. If we don't do this, our vphysics shadow will not update.
Teleport( NULL, &m_vecAngle2, NULL );
m_toggle_state = TS_AT_TOP;
}
else
{
m_toggle_state = TS_AT_BOTTOM;
}
#ifdef HL1_DLL
SetSolid( SOLID_VPHYSICS );
#endif
// Slam the object back to solid - if we really want it to be solid.
if ( m_bSolidBsp )
{
SetSolid( SOLID_BSP );
}
}
//-----------------------------------------------------------------------------
bool CRotDoor::CreateVPhysics()
{
if ( !IsSolidFlagSet( FSOLID_NOT_SOLID ) )
{
VPhysicsInitShadow( false, false );
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : state -
//-----------------------------------------------------------------------------
// This is ONLY used by the node graph to test movement through a door
void CRotDoor::SetToggleState( int state )
{
if ( state == TS_AT_TOP )
SetLocalAngles( m_vecAngle2 );
else
SetLocalAngles( m_vecAngle1 );
}