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.
1433 lines
41 KiB
1433 lines
41 KiB
//========= 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 ); |
|
}
|
|
|