// 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; nm_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; iheight - 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; ibutton; 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; icurtime + 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; iheight ) < 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; iheight ) < 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; iGetEntityName()); } 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; iGetPlayerName() ); EntityText( text_offset, tempstr, 0 ); ++text_offset; } } return text_offset; } //--------------------------------------------------------------------------------------------------------