|
|
// 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; }
//--------------------------------------------------------------------------------------------------------
|