Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.

760 lines
20 KiB

// func_elevator.cpp
// Copyright 2007 Turtle Rock Studios, Inc.
#include "cbase.h"
#include "func_elevator.h"
#include "nav.h"
// NOTE: This has to be the last file included!
#include "tier0/memdbgon.h"
ConVar ZombieAirborneElevator( "z_elevator_in_air", "0" );
//--------------------------------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( info_elevator_floor, CInfoElevatorFloor );
//--------------------------------------------------------------------------------------------------------
BEGIN_DATADESC( CInfoElevatorFloor )
// Outputs
DEFINE_OUTPUT( m_OnReachedFloor, "OnReachedFloor" ),
END_DATADESC()
//--------------------------------------------------------------------------------------------------------
void CInfoElevatorFloor::OnReachedFloor( CBaseEntity *elevator )
{
m_OnReachedFloor.FireOutput( elevator, elevator );
}
//--------------------------------------------------------------------------------------------------------
LINK_ENTITY_TO_CLASS( func_elevator, CFuncElevator );
//--------------------------------------------------------------------------------------------------------
BEGIN_DATADESC( CFuncElevator )
DEFINE_KEYFIELD( m_topFloorPosition, FIELD_POSITION_VECTOR, "top" ),
DEFINE_KEYFIELD( m_bottomFloorPosition, FIELD_POSITION_VECTOR, "bottom" ),
DEFINE_KEYFIELD( m_maxSpeed, FIELD_FLOAT, "speed"),
DEFINE_KEYFIELD( m_acceleration, FIELD_FLOAT, "acceleration"),
DEFINE_KEYFIELD( m_soundStart, FIELD_SOUNDNAME, "StartSound" ),
DEFINE_KEYFIELD( m_soundStop, FIELD_SOUNDNAME, "StopSound" ),
DEFINE_KEYFIELD( m_soundDisable, FIELD_SOUNDNAME, "DisableSound" ),
DEFINE_FIELD( m_currentSound, FIELD_SOUNDNAME ),
DEFINE_KEYFIELD( m_flBlockDamage, FIELD_FLOAT, "BlockDamage"),
// Inputs
DEFINE_INPUTFUNC( FIELD_STRING, "MoveToFloor", InputMoveToFloor ),
DEFINE_INPUTFUNC( FIELD_STRING, "Disable", InputDisable ),
// Outputs
DEFINE_OUTPUT( m_OnReachedTop, "OnReachedTop" ),
DEFINE_OUTPUT( m_OnReachedBottom, "OnReachedBottom" ),
// Functions
DEFINE_FUNCTION( StopMoveSoundThink ),
// FIXMEL4DTOMAINMERGE
//DEFINE_FUNCTION( AccelerationThink ),
END_DATADESC()
void SendProxy_Origin( const SendProp *pProp, const void *pStruct, const void *pData, DVariant *pOut, int iElement, int objectID );
//--------------------------------------------------------------------------------------------------------
IMPLEMENT_SERVERCLASS_ST(CFuncElevator, DT_FuncElevator)
SendPropExclude( "DT_BaseEntity", "m_vecOrigin" ),
SendPropVector (SENDINFO(m_vecOrigin), -1, SPROP_NOSCALE|SPROP_CHANGES_OFTEN, 0.0f, HIGH_DEFAULT, SendProxy_Origin ),
SendPropFloat( SENDINFO( m_acceleration ), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO( m_currentSpeed ), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO( m_movementStartTime ), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO( m_movementStartSpeed ), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO( m_movementStartZ ), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO( m_destinationFloorPosition ), 0, SPROP_NOSCALE ),
SendPropFloat( SENDINFO( m_maxSpeed ), 0, SPROP_NOSCALE ),
SendPropBool( SENDINFO( m_isMoving ) ),
END_SEND_TABLE()
//--------------------------------------------------------------------------------------------------------
CFuncElevator::CFuncElevator()
{
}
//--------------------------------------------------------------------------------------------------------
static int FloorHeightSort( const FloorInfo *a, const FloorInfo *b )
{
return a->height > b->height;
}
//--------------------------------------------------------------------------------------------------------
void CFuncElevator::Spawn( void )
{
SetMoveType( MOVETYPE_CUSTOM );
SetModel( STRING( GetModelName() ) );
SetTouch( NULL );
Precache();
AddFlag( FL_CONVEYOR );
// It is solid?
SetSolid( SOLID_BSP );
if ( m_acceleration == 0.0f )
{
m_acceleration = m_maxSpeed; // 1 second acceleration period
}
m_enabled = true;
// Construct a list of data for each floor
m_floors.RemoveAll();
FloorInfo floor;
floor.button = NULL; // filled in later
floor.height = m_bottomFloorPosition.z;
floor.name = AllocPooledString( "bottom" );
m_floors.AddToHead( floor );
// Floors are specified by CInfoElevatorFloor entities
floor.button = NULL; // filled in later
floor.height = m_topFloorPosition.z;
floor.name = AllocPooledString( "top" );
m_floors.AddToTail( floor );
// Trawl through the list of entities that have map-specified outputs that target this elevator. Grab any that
// are func_buttons that send a MoveToFloor output to us, and save off which floor they go to.
CBaseEntity *inputSource = NULL;
inputSource = gEntList.FindEntityByOutputTarget( NULL, GetEntityName() );
while ( inputSource )
{
datamap_t *dmap = inputSource->GetDataDescMap();
bool found = false;
while ( dmap && !found )
{
int fields = dmap->dataNumFields;
for ( int i = 0; i < fields; i++ )
{
typedescription_t *dataDesc = &dmap->dataDesc[i];
if ( ( dataDesc->fieldType == FIELD_CUSTOM ) && ( dataDesc->flags & FTYPEDESC_OUTPUT ) )
{
CBaseEntityOutput *pOutput = (CBaseEntityOutput *)((int)inputSource + (int)dataDesc->fieldOffset);
const CEventAction *action = pOutput->GetActionForTarget( GetEntityName() );
if ( action )
{
if ( FStrEq( STRING( action->m_iTargetInput ), "MoveToFloor" ) && action->m_iParameter != NULL_STRING )
{
bool isButton = inputSource->ClassMatches( "func_button*" );
bool existingFloor = false;
for ( int n=0; n<m_floors.Count(); ++n )
{
FloorInfo &floor = m_floors[n];
if ( floor.name == action->m_iParameter )
{
if ( isButton )
{
floor.button = inputSource;
}
existingFloor = true;
break;
}
}
if ( !existingFloor )
{
// See if it's a real floor
CBaseEntity *floorEntity = gEntList.FindEntityByName( NULL, action->m_iParameter );
if ( floorEntity )
{
floor.button = (isButton) ? inputSource : NULL;
floor.height = floorEntity->GetAbsOrigin().z;
floor.name = action->m_iParameter;
m_floors.AddToTail( floor );
}
}
found = true;
break;
}
}
}
}
dmap = dmap->baseMap;
}
inputSource = gEntList.FindEntityByOutputTarget( inputSource, GetEntityName() );
}
m_floors.Sort( FloorHeightSort );
m_movementStartTime = gpGlobals->curtime;
m_movementStartSpeed = m_currentSpeed;
m_movementStartZ = GetAbsOrigin().z;
m_destinationFloorPosition = GetAbsOrigin().z;
SetThink(NULL);
m_targetFloor = NULL;
m_accelerationTimer.Start();
m_isMoving = false;
CreateVPhysics();
}
//--------------------------------------------------------------------------------------------------------
bool CFuncElevator::CreateVPhysics( void )
{
VPhysicsInitShadow(false, false);
return true;
}
//--------------------------------------------------------------------------------------------------------
void CFuncElevator::Precache( void )
{
if ( m_soundStart != NULL_STRING )
{
PrecacheScriptSound( STRING( m_soundStart ) );
}
if ( m_soundStop != NULL_STRING )
{
PrecacheScriptSound( STRING( m_soundStop ) );
}
if ( m_soundDisable != NULL_STRING )
{
PrecacheScriptSound( STRING( m_soundDisable ) );
}
m_currentSound = NULL_STRING;
}
//--------------------------------------------------------------------------------------------------------
int CFuncElevator::GetFloorForHeight( float height ) const
{
int bestFloor = -1;
float bestHeightDelta = 100.0f;
for ( int i=0; i<GetNumFloors(); ++i )
{
const FloorInfo *floor = GetFloor( i );
float heightDelta = fabs( floor->height - height );
if ( heightDelta < bestHeightDelta )
{
bestFloor = i;
bestHeightDelta = heightDelta;
}
}
return bestFloor;
}
//--------------------------------------------------------------------------------------------------------
EHANDLE CFuncElevator::GetButtonForHeight( float height ) const
{
int targetFloorIndex = GetFloorForHeight( height );
if ( targetFloorIndex < 0 || targetFloorIndex >= GetNumFloors() )
return NULL;
const FloorInfo *targetFloor = GetFloor( targetFloorIndex );
if ( !targetFloor )
return NULL;
return targetFloor->button;
}
//--------------------------------------------------------------------------------------------------------
EHANDLE CFuncElevator::GetButtonAtCurrentHeight( void ) const
{
int currentFloorIndex = GetCurrentFloor();
if ( currentFloorIndex < 0 || currentFloorIndex >= GetNumFloors() )
return NULL;
const FloorInfo *currentFloor = GetFloor( currentFloorIndex );
CBaseEntity *bestButton = NULL;
float bestHeightDelta = 100.0f;
for ( int i=0; i<GetNumFloors(); ++i )
{
const FloorInfo *floor = GetFloor( i );
if ( floor == currentFloor )
continue;
CBaseEntity *button = floor->button;
if ( !button )
continue;
float heightDelta = fabs( button->WorldSpaceCenter().z - currentFloor->height );
if ( heightDelta < bestHeightDelta )
{
bestHeightDelta = heightDelta;
bestButton = button;
}
}
return bestButton;
}
//--------------------------------------------------------------------------------------------------------
void CFuncElevator::MoveTo( float destinationZ )
{
if ( !m_enabled )
return;
m_isMoving = true;
m_accelerationTimer.Start();
m_movementStartTime = gpGlobals->curtime;
m_movementStartSpeed = m_currentSpeed;
m_movementStartZ = GetAbsOrigin().z;
m_destinationFloorPosition = destinationZ;
if ( m_soundStart != NULL_STRING )
{
if (m_currentSound == m_soundStart)
{
StopSound(entindex(), CHAN_BODY, (char*)STRING(m_soundStop));
}
else
{
m_currentSound = m_soundStart;
CPASAttenuationFilter filter( this );
EmitSound_t ep;
ep.m_nChannel = CHAN_BODY;
ep.m_pSoundName = (char*)STRING(m_soundStart);
ep.m_flVolume = 1;
ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, entindex(), ep );
}
}
// Find any physics objects on the elevator and destroy them.
// This is to address a problem where players and bots fall through the elevator
// if they stand on a physics object while the elevator is in motion.
Vector lo, hi;
GetCollideable()->WorldSpaceSurroundingBounds( &lo, &hi );
hi.z += HumanHeight;
lo.z -= HumanHeight;
CBaseEntity *entitiesList[ 128 ];
CFlaggedEntitiesEnum enumerator( entitiesList, 128, 0 );
UTIL_EntitiesInBox( lo, hi, &enumerator );
for ( int i=0; i<enumerator.GetCount(); ++i )
{
CBaseEntity *pEnt = entitiesList[i];
// We're only concerned about physics props.
if ( FClassnameIs( pEnt, "prop_physics" ) )
{
// Since it may not be a breakable prop, we can't do TakeDamage.
// Just remove it.
UTIL_Remove( pEnt );
}
}
// Clear think (that stops sounds)
SetThink(NULL);
}
//--------------------------------------------------------------------------------------------------------
void CFuncElevator::InputDisable( inputdata_t &inputdata )
{
if ( !m_enabled )
return;
if ( m_currentSpeed != 0.0f )
{
// Move to our current location, to cancel any current movement...
MoveTo( GetAbsOrigin().z );
}
SetThink(&CFuncElevator::StopMoveSoundThink);
SetNextThink( gpGlobals->curtime + 0.1f );
// ... and disable ourselves to prevent any future movement.
m_enabled = false;
}
//--------------------------------------------------------------------------------------------------------
void CFuncElevator::InputMoveToFloor( inputdata_t &inputdata )
{
if ( !m_enabled )
return;
const char *floorName = inputdata.value.String();
m_targetFloor = NULL;
if ( FStrEq( floorName, "top" ) )
{
MoveTo( m_topFloorPosition.z );
}
else if ( FStrEq( floorName, "bottom" ) )
{
MoveTo( m_bottomFloorPosition.z );
}
else
{
CBaseEntity *target = gEntList.FindEntityByName( NULL, floorName );
if ( target )
{
m_targetFloor = target;
MoveTo( target->GetAbsOrigin().z );
}
else
{
Warning( "Elevator tried to move to bad floor '%s'\n", floorName );
return;
}
}
}
//--------------------------------------------------------------------------------------------------------
/**
* Returns the current floor, or -1 if the elevator is in-between floors
*/
int CFuncElevator::GetCurrentFloor( void ) const
{
float currentHeight = GetAbsOrigin().z;
const float Tolerance = 0.5f;
for ( int i=0; i<m_floors.Count(); ++i )
{
const FloorInfo *floor = GetFloor( i );
if ( fabs( currentHeight - floor->height ) < Tolerance )
{
return i;
}
}
return -1; // in-between floors
}
//--------------------------------------------------------------------------------------------------------
/**
* Returns the floor to which the elevator is moving, or the current floor number if the elevator is stopped
*/
int CFuncElevator::GetDestinationFloor( void ) const
{
if ( m_currentSpeed != 0.0f )
{
float targetHeight = m_destinationFloorPosition;
const float Tolerance = 0.5f;
for ( int i=0; i<m_floors.Count(); ++i )
{
const FloorInfo *floor = GetFloor( i );
if ( fabs( targetHeight - floor->height ) < Tolerance )
{
return i;
}
}
}
return GetCurrentFloor();
}
//--------------------------------------------------------------------------------------------------------
void CFuncElevator::MoveDone( void )
{
SetContextThink( NULL, TICK_NEVER_THINK, "AccelerationContext" );
m_currentSpeed = 0.0f;
m_isMoving = false;
// Stop sounds at the next think, rather than here as another
// SetPosition call might immediately follow the end of this move
SetThink(&CFuncElevator::StopMoveSoundThink);
SetNextThink( gpGlobals->curtime + 0.1f );
BaseClass::MoveDone();
// Sets a floor string and fires the output
float currentPosition = GetAbsOrigin().z;
if ( currentPosition >= m_topFloorPosition.z )
{
m_OnReachedTop.FireOutput( this, this );
}
else if ( currentPosition <= m_bottomFloorPosition.z )
{
m_OnReachedBottom.FireOutput( this, this );
}
else if ( m_targetFloor.Get() != NULL )
{
CInfoElevatorFloor *floor = dynamic_cast< CInfoElevatorFloor * >(m_targetFloor.Get());
if ( floor )
{
floor->OnReachedFloor( this );
}
}
}
//--------------------------------------------------------------------------------------------------------
void CFuncElevator::Blocked( CBaseEntity *pOther )
{
// Hurt the blocker
if ( m_flBlockDamage )
{
/*
if ( pOther->GetTeamNumber() == TEAM_SURVIVOR )
{
// realistically, we still want to kill them if they're blocked against something not in our move hierarchy.
const trace_t &touchTrace = GetTouchTrace();
if ( touchTrace.DidHitNonWorldEntity() )
{
return;
}
}
*/
pOther->TakeDamage( CTakeDamageInfo( this, this, m_flBlockDamage, DMG_CRUSH ) );
}
}
//--------------------------------------------------------------------------------------------------------
void CFuncElevator::StopMoveSoundThink( void )
{
string_t targetSound = ( m_enabled ) ? m_soundStop : m_soundDisable;
if ( m_currentSound != NULL_STRING && ( m_currentSound != targetSound ) )
{
StopSound( entindex(), CHAN_BODY, STRING( m_currentSound ) );
}
if ( targetSound != NULL_STRING && ( m_currentSound != targetSound ) )
{
m_currentSound = targetSound;
CPASAttenuationFilter filter( this );
EmitSound_t ep;
ep.m_nChannel = CHAN_BODY;
ep.m_pSoundName = STRING( targetSound );
ep.m_flVolume = 1;
ep.m_SoundLevel = SNDLVL_NORM;
EmitSound( filter, entindex(), ep );
}
SetThink(NULL);
}
//--------------------------------------------------------------------------------------------------------
bool CFuncElevator::IsPlayerOnElevator( CBasePlayer *player )
{
if ( !player->IsAlive() )
return false;
CBaseEntity *ground = player->GetGroundEntity();
if ( ground )
{
CBaseEntity *groundParent = ground->GetParent();
while ( groundParent && ground->GetParent() && ground->GetParent() != groundParent )
{
groundParent = ground->GetParent();
}
CBaseEntity *groundMoveParent = ground->GetMoveParent();
while ( groundMoveParent && ground->GetMoveParent() && ground->GetMoveParent() != groundMoveParent )
{
groundMoveParent = ground->GetMoveParent();
}
if ( ground == this || groundMoveParent == this || groundParent == this )
{
return true;
}
}
if ( ZombieAirborneElevator.GetBool() )
{
Extent extent;
GetCollideable()->WorldSpaceSurroundingBounds( &extent.lo, &extent.hi );
extent.hi.z += HumanHeight;
if ( extent.Contains( player->GetAbsOrigin() ) )
{
return true;
}
}
return false;
}
//--------------------------------------------------------------------------------------------------------
class ElevatorPlayerCollector
{
public:
ElevatorPlayerCollector( CBaseEntity *elevator, int team )
{
m_elevator = elevator;
m_team = team;
elevator->GetCollideable()->WorldSpaceSurroundingBounds( &m_extent.lo, &m_extent.hi );
m_extent.hi.z += HumanHeight;
}
bool operator()( CBasePlayer *player )
{
if ( !player->IsAlive() )
return true;
if ( player->GetTeamNumber() != m_team && m_team != TEAM_UNASSIGNED )
return true;
CBaseEntity *ground = player->GetGroundEntity();
if ( ground )
{
CBaseEntity *groundParent = ground->GetParent();
while ( groundParent && ground->GetParent() && ground->GetParent() != groundParent )
{
groundParent = ground->GetParent();
}
CBaseEntity *groundMoveParent = ground->GetMoveParent();
while ( groundMoveParent && ground->GetMoveParent() && ground->GetMoveParent() != groundMoveParent )
{
groundMoveParent = ground->GetMoveParent();
}
if ( ground == m_elevator || groundMoveParent == m_elevator || groundParent == m_elevator )
{
m_players.AddToTail( player );
return true;
}
}
if ( m_extent.Contains( player->GetAbsOrigin() ) )
{
m_players.AddToTail( player );
return true;
}
return true;
}
CUtlVector< CBasePlayer * > m_players;
CBaseEntity *m_elevator;
int m_team;
Extent m_extent;
};
//--------------------------------------------------------------------------------------------------------
void CFuncElevator::FindPlayersOnElevator( CUtlVector< CBasePlayer * > *players, int teamNumber )
{
ElevatorPlayerCollector playerCollector( this, teamNumber );
ForEachPlayer( playerCollector );
*players = playerCollector.m_players;
}
//--------------------------------------------------------------------------------------------------------
/**
* Draw any debug text overlays, and return the text offset from the top
*/
int CFuncElevator::DrawDebugTextOverlays( void )
{
int text_offset = BaseClass::DrawDebugTextOverlays();
if ( m_debugOverlays & OVERLAY_TEXT_BIT )
{
char tempstr[512];
if ( GetCurrentSpeed() != 0.0f )
{
int destinationFloor = GetDestinationFloor();
const char *floorName = "unknown";
const FloorInfo *floor = GetFloor( destinationFloor );
if ( floor )
{
floorName = STRING( floor->name );
}
Q_snprintf( tempstr, sizeof(tempstr), "Moving at speed %f to floor %d(%s)",
GetCurrentSpeed(), destinationFloor, floorName );
EntityText( text_offset, tempstr, 0 );
++text_offset;
}
else
{
int currentFloor = GetCurrentFloor();
const char *floorName = "unknown";
const FloorInfo *floor = GetFloor( currentFloor );
if ( floor )
{
floorName = STRING( floor->name );
}
Q_snprintf( tempstr, sizeof(tempstr), "Currently at floor %d(%s)",
currentFloor, floorName );
EntityText( text_offset, tempstr, 0 );
++text_offset;
}
CBaseEntity *nearbyButton = GetButtonAtCurrentHeight();
if ( nearbyButton )
{
Q_snprintf( tempstr, sizeof(tempstr), "Nearby button is %s",
STRING( nearbyButton->GetEntityName() ) );
EntityText( text_offset++, tempstr, 0 );
}
else
{
EntityText( text_offset++, "No nearby buttons", 0 );
}
for ( int i=0; i<m_floors.Count(); ++i )
{
const FloorInfo &floor = m_floors[i];
const char *floorName = STRING(floor.name);
float floorHeight = floor.height;
const char *buttonName = "<none>";
CBaseEntity *buttonEntity = GetButtonForHeight( floorHeight );
if ( buttonEntity != NULL )
{
buttonName = STRING(buttonEntity->GetEntityName());
}
Q_snprintf( tempstr, sizeof(tempstr), "Floor %s is at %f, triggered by %s",
floorName, floorHeight, buttonName );
EntityText( text_offset++, tempstr, 0 );
}
CUtlVector< CBasePlayer * > players;
FindPlayersOnElevator( &players );
for ( int i=0; i<players.Count(); ++i )
{
Q_snprintf( tempstr, sizeof(tempstr), "Occupant %d: %s", i, players[i]->GetPlayerName() );
EntityText( text_offset, tempstr, 0 );
++text_offset;
}
}
return text_offset;
}
//--------------------------------------------------------------------------------------------------------