//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include "cbase.h" #include "ai_basenpc.h" #include "ai_senses.h" #include "ai_memory.h" #include "engine/IEngineSound.h" #include "ammodef.h" #include "Sprite.h" #include "hl2/hl2_player.h" #include "soundenvelope.h" #include "explode.h" #include "IEffects.h" #include "animation.h" #include "props.h" #include "rope.h" #include "rope_shared.h" #include "basehlcombatweapon_shared.h" #include "iservervehicle.h" #include "physics_prop_ragdoll.h" #include "portal_util_shared.h" #include "prop_portal.h" #include "portal_player.h" #include "world.h" #include "ai_baseactor.h" // for Glados ent playing VCDs #include "sceneentity.h" // precacheing vcds // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define SECURITY_CAMERA_MODEL "models/props/security_camera.mdl" #define SECURITY_CAMERA_BC_YAW "aim_yaw" #define SECURITY_CAMERA_BC_PITCH "aim_pitch" #define SECURITY_CAMERA_RANGE 1500 #define SECURITY_CAMERA_SPREAD VECTOR_CONE_2DEGREES #define SECURITY_CAMERA_MAX_WAIT 5 #define SECURITY_CAMERA_PING_TIME 1.0f //LPB!! #define SECURITY_CAMERA_NUM_ROPES 2 #define SECURITY_CAMERA_GLOW_SPRITE "sprites/glow1.vmt" //Aiming variables #define SECURITY_CAMERA_MAX_NOHARM_PERIOD 0.0f #define SECURITY_CAMERA_MAX_GRACE_PERIOD 3.0f //Spawnflags #define SF_SECURITY_CAMERA_AUTOACTIVATE 0x00000020 #define SF_SECURITY_CAMERA_STARTINACTIVE 0x00000040 #define SF_SECURITY_CAMERA_NEVERRETIRE 0x00000080 #define SF_SECURITY_CAMERA_OUT_OF_AMMO 0x00000100 #define CAMERA_DESTROYED_SCENE_1 "scenes/general/generic_security_camera_destroyed-1.vcd" #define CAMERA_DESTROYED_SCENE_2 "scenes/general/generic_security_camera_destroyed-2.vcd" #define CAMERA_DESTROYED_SCENE_3 "scenes/general/generic_security_camera_destroyed-3.vcd" #define CAMERA_DESTROYED_SCENE_4 "scenes/general/generic_security_camera_destroyed-4.vcd" #define CAMERA_DESTROYED_SCENE_5 "scenes/general/generic_security_camera_destroyed-5.vcd" //Heights #define SECURITY_CAMERA_YAW_SPEED 7.0f #define SECURITY_CAMERA_TOTAL_TO_KNOCK_DOWN 33 //Turret states enum turretState_e { TURRET_SEARCHING, TURRET_AUTO_SEARCHING, TURRET_ACTIVE, TURRET_DEPLOYING, TURRET_RETIRING, TURRET_DEAD, }; // Forces glados actor to play reaction scenes when player dismounts camera. void PlayDismountSounds( void ); // // Security Camera // class CNPC_SecurityCamera : public CNPCBaseInteractive, public CDefaultPlayerPickupVPhysics { DECLARE_CLASS( CNPC_SecurityCamera, CNPCBaseInteractive ); public: CNPC_SecurityCamera( void ); ~CNPC_SecurityCamera( void ); void Precache( void ); virtual void CreateSounds( void ); virtual void StopLoopingSounds( void ); virtual void Spawn( void ); virtual void Activate( void ); bool CreateVPhysics( void ); virtual void UpdateOnRemove( void ); virtual void NotifySystemEvent( CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t ¶ms ); virtual int ObjectCaps( void ); void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); // Think functions void Retire( void ); void Deploy( void ); void ActiveThink( void ); void SearchThink( void ); void DeathThink( void ); // Inputs void InputToggle( inputdata_t &inputdata ); void InputEnable( inputdata_t &inputdata ); void InputDisable( inputdata_t &inputdata ); void InputRagdoll( inputdata_t &inputdata ); void SetLastSightTime(); int OnTakeDamage( const CTakeDamageInfo &inputInfo ); virtual void PlayerPenetratingVPhysics( void ); bool OnAttemptPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ); bool ShouldSavePhysics() { return true; } virtual bool CanBeAnEnemyOf( CBaseEntity *pEnemy ); Class_T Classify( void ) { if( m_bEnabled ) return CLASS_COMBINE; return CLASS_NONE; } bool FVisible( CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL ); Vector EyeOffset( Activity nActivity ) { Vector vForward; GetVectors( &vForward, 0, 0 ); return vForward * 10.0f; } Vector EyePosition( void ) { return GetAbsOrigin() + EyeOffset(GetActivity()); } protected: bool PreThink( turretState_e state ); void Ping( void ); void Toggle( void ); void Enable( void ); void Disable( void ); void RopesOn( void ); void RopesOff( void ); void EyeOn( void ); void EyeOff( void ); bool UpdateFacing( void ); private: CHandle m_hRopes[ SECURITY_CAMERA_NUM_ROPES ]; CHandle m_hEyeGlow; bool m_bAutoStart; bool m_bActive; //Denotes the turret is deployed and looking for targets bool m_bBlinkState; bool m_bEnabled; //Denotes whether the turret is able to deploy or not float m_flLastSight; float m_flPingTime; QAngle m_vecGoalAngles; QAngle m_vecCurrentAngles; Vector m_vNoisePos; int m_iTicksTillNextNoise; CSoundPatch *m_pMovementSound; COutputEvent m_OnDeploy; COutputEvent m_OnRetire; DECLARE_DATADESC(); }; //Datatable BEGIN_DATADESC( CNPC_SecurityCamera ) DEFINE_ARRAY( m_hRopes, FIELD_EHANDLE, SECURITY_CAMERA_NUM_ROPES ), DEFINE_FIELD( m_hEyeGlow, FIELD_EHANDLE ), DEFINE_FIELD( m_bAutoStart, FIELD_BOOLEAN ), DEFINE_FIELD( m_bActive, FIELD_BOOLEAN ), DEFINE_FIELD( m_bBlinkState, FIELD_BOOLEAN ), DEFINE_FIELD( m_bEnabled, FIELD_BOOLEAN ), DEFINE_FIELD( m_flLastSight, FIELD_TIME ), DEFINE_FIELD( m_flPingTime, FIELD_TIME ), DEFINE_FIELD( m_vecGoalAngles, FIELD_VECTOR ), DEFINE_FIELD( m_vecCurrentAngles, FIELD_VECTOR ), DEFINE_FIELD( m_vNoisePos, FIELD_VECTOR ), DEFINE_FIELD( m_iTicksTillNextNoise, FIELD_INTEGER ), DEFINE_SOUNDPATCH( m_pMovementSound ), DEFINE_THINKFUNC( Retire ), DEFINE_THINKFUNC( Deploy ), DEFINE_THINKFUNC( ActiveThink ), DEFINE_THINKFUNC( SearchThink ), DEFINE_THINKFUNC( DeathThink ), // Inputs DEFINE_INPUTFUNC( FIELD_VOID, "Toggle", InputToggle ), DEFINE_INPUTFUNC( FIELD_VOID, "Enable", InputEnable ), DEFINE_INPUTFUNC( FIELD_VOID, "Disable", InputDisable ), DEFINE_INPUTFUNC( FIELD_VOID, "Ragdoll", InputRagdoll ), DEFINE_OUTPUT( m_OnDeploy, "OnDeploy" ), DEFINE_OUTPUT( m_OnRetire, "OnRetire" ), END_DATADESC() LINK_ENTITY_TO_CLASS( npc_security_camera, CNPC_SecurityCamera ); //----------------------------------------------------------------------------- // Constructor //----------------------------------------------------------------------------- CNPC_SecurityCamera::CNPC_SecurityCamera( void ) { m_bActive = false; m_bAutoStart = false; m_flPingTime = 0; m_flLastSight = 0; m_bBlinkState = false; m_bEnabled = false; m_vecCurrentAngles = QAngle( 0.0f, 0.0f, 0.0f ); m_vecGoalAngles.Init(); m_vNoisePos = Vector( 0.0f, 0.0f, 0.0f ); m_iTicksTillNextNoise = 5; m_pMovementSound = NULL; m_hEyeGlow = NULL; } CNPC_SecurityCamera::~CNPC_SecurityCamera( void ) { } //----------------------------------------------------------------------------- // Purpose: Precache //----------------------------------------------------------------------------- void CNPC_SecurityCamera::Precache( void ) { PrecacheModel( SECURITY_CAMERA_MODEL ); PrecacheScriptSound( "Portalgun.pedestal_rotate_loop" ); // Scenes for when the player dismounts a security camera. Spoken only if Aperture_AI actor is in the PrecacheInstancedScene( CAMERA_DESTROYED_SCENE_1 ); PrecacheInstancedScene( CAMERA_DESTROYED_SCENE_2 ); PrecacheInstancedScene( CAMERA_DESTROYED_SCENE_3 ); PrecacheInstancedScene( CAMERA_DESTROYED_SCENE_4 ); PrecacheInstancedScene( CAMERA_DESTROYED_SCENE_5 ); BaseClass::Precache(); } void CNPC_SecurityCamera::CreateSounds() { if (!m_pMovementSound) { CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); CPASAttenuationFilter filter( this ); m_pMovementSound = controller.SoundCreate( filter, entindex(), "Portalgun.pedestal_rotate_loop" ); controller.Play( m_pMovementSound, 0, 100 ); } } void CNPC_SecurityCamera::StopLoopingSounds() { CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); controller.SoundDestroy( m_pMovementSound ); m_pMovementSound = NULL; BaseClass::StopLoopingSounds(); } //----------------------------------------------------------------------------- // Purpose: Spawn the entity //----------------------------------------------------------------------------- void CNPC_SecurityCamera::Spawn( void ) { Precache(); SetModel( SECURITY_CAMERA_MODEL ); BaseClass::Spawn(); m_HackedGunPos = Vector( 0, 0, 12.75 ); SetViewOffset( EyeOffset( ACT_IDLE ) ); m_flFieldOfView = VIEW_FIELD_FULL; m_takedamage = DAMAGE_NO; m_iHealth = 1000; m_bloodColor = BLOOD_COLOR_MECH; SetSolid( SOLID_BBOX ); AddSolidFlags( FSOLID_NOT_STANDABLE ); SetCollisionBounds( Vector( -16.0f, -16.0f, -16.0f ), Vector( 16.0f, 16.0f, 16.0f ) ); RemoveFlag( FL_AIMTARGET ); AddEFlags( EFL_NO_DISSOLVE ); SetPoseParameter( SECURITY_CAMERA_BC_YAW, 0 ); SetPoseParameter( SECURITY_CAMERA_BC_PITCH, 0 ); //Set our autostart state m_bAutoStart = !!( m_spawnflags & SF_SECURITY_CAMERA_AUTOACTIVATE ); m_bEnabled = ( ( m_spawnflags & SF_SECURITY_CAMERA_STARTINACTIVE ) == false ); //Do we start active? if ( m_bAutoStart && m_bEnabled ) { SetThink( &CNPC_SecurityCamera::SearchThink ); } //Stagger our starting times SetNextThink( gpGlobals->curtime + random->RandomFloat( 0.1f, 0.3f ) ); CreateVPhysics(); } void CNPC_SecurityCamera::Activate( void ) { BaseClass::Activate(); CreateSounds(); RopesOn(); EyeOn(); } bool CNPC_SecurityCamera::CreateVPhysics( void ) { IPhysicsObject *pPhysics = VPhysicsInitNormal( SOLID_VPHYSICS, FSOLID_NOT_STANDABLE, false ); if ( !pPhysics ) DevMsg( "npc_turret_floor unable to spawn physics object!\n" ); else pPhysics->EnableMotion( false ); return true; } void CNPC_SecurityCamera::UpdateOnRemove( void ) { RopesOff(); EyeOff(); BaseClass::UpdateOnRemove(); } void CNPC_SecurityCamera::NotifySystemEvent(CBaseEntity *pNotify, notify_system_event_t eventType, const notify_system_event_params_t ¶ms ) { // On teleport, we record a pointer to the portal we are arriving at if ( eventType == NOTIFY_EVENT_TELEPORT ) { RopesOff(); RopesOn(); } BaseClass::NotifySystemEvent( pNotify, eventType, params ); } int CNPC_SecurityCamera::ObjectCaps( void ) { IPhysicsObject *pPhysics = VPhysicsGetObject(); if ( !pPhysics || !pPhysics->IsMotionEnabled() ) return BaseClass::ObjectCaps(); return ( BaseClass::ObjectCaps() | FCAP_USE_IN_RADIUS | FCAP_USE_ONGROUND | FCAP_IMPULSE_USE ); } void CNPC_SecurityCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { CBasePlayer *pPlayer = ToBasePlayer( pActivator ); if ( pPlayer ) pPlayer->PickupObject( this ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CNPC_SecurityCamera::OnTakeDamage( const CTakeDamageInfo &inputInfo ) { if ( !m_takedamage ) return 0; CTakeDamageInfo info = inputInfo; if ( m_bActive == false ) info.ScaleDamage( 0.1f ); m_iHealth -= info.GetDamage(); if ( m_iHealth <= 0 ) { m_iHealth = 0; m_takedamage = DAMAGE_NO; RemoveFlag( FL_NPC ); // why are they set in the first place??? ExplosionCreate( GetAbsOrigin(), GetLocalAngles(), this, 100, 100, false ); SetThink( &CNPC_SecurityCamera::DeathThink ); StopSound( "NPC_SecurityCamera.Alert" ); m_OnDamaged.FireOutput( info.GetInflictor(), this ); SetNextThink( gpGlobals->curtime + 0.1f ); return 0; } return 1; } //----------------------------------------------------------------------------- // Purpose: We override this code because otherwise we start to move into the // tricky realm of player avoidance. Since we don't go through the // normal NPC thinking but we ARE an NPC (...) we miss a bunch of // book keeping. This means we can become invisible and then never // reappear. //----------------------------------------------------------------------------- void CNPC_SecurityCamera::PlayerPenetratingVPhysics( void ) { // We don't care! } bool CNPC_SecurityCamera::OnAttemptPhysGunPickup( CBasePlayer *pPhysGunUser, PhysGunPickup_t reason ) { return !m_bActive; } //----------------------------------------------------------------------------- // Purpose: Shut down //----------------------------------------------------------------------------- void CNPC_SecurityCamera::Retire( void ) { if ( PreThink( TURRET_RETIRING ) ) return; //Level out the turret m_vecGoalAngles = GetAbsAngles(); SetNextThink( gpGlobals->curtime ); //Set ourselves to close if ( m_bActive ) { //Notify of the retraction m_OnRetire.FireOutput( NULL, this ); } m_bActive = false; m_flLastSight = 0; SetThink( &CNPC_SecurityCamera::SUB_DoNothing ); } //----------------------------------------------------------------------------- // Purpose: Start up //----------------------------------------------------------------------------- void CNPC_SecurityCamera::Deploy( void ) { if ( PreThink( TURRET_DEPLOYING ) ) return; m_vecGoalAngles = GetAbsAngles(); SetNextThink( gpGlobals->curtime ); if ( !m_bActive ) { m_bActive = true; //Notify we're deploying m_OnDeploy.FireOutput( NULL, this ); } m_flPlaybackRate = 0; SetThink( &CNPC_SecurityCamera::SearchThink ); //EmitSound( "NPC_SecurityCamera.Move" ); SetLastSightTime(); } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- void CNPC_SecurityCamera::SetLastSightTime() { if( HasSpawnFlags( SF_SECURITY_CAMERA_NEVERRETIRE ) ) { m_flLastSight = FLT_MAX; } else { m_flLastSight = gpGlobals->curtime + SECURITY_CAMERA_MAX_WAIT; } } //----------------------------------------------------------------------------- // Purpose: Causes the turret to face its desired angles //----------------------------------------------------------------------------- bool CNPC_SecurityCamera::UpdateFacing( void ) { bool bMoved = false; if ( m_vecCurrentAngles.x < m_vecGoalAngles.x ) { m_vecCurrentAngles.x += SECURITY_CAMERA_YAW_SPEED; if ( m_vecCurrentAngles.x > m_vecGoalAngles.x ) m_vecCurrentAngles.x = m_vecGoalAngles.x; bMoved = true; } if ( m_vecCurrentAngles.y < m_vecGoalAngles.y ) { m_vecCurrentAngles.y += SECURITY_CAMERA_YAW_SPEED; if ( m_vecCurrentAngles.y > m_vecGoalAngles.y ) m_vecCurrentAngles.y = m_vecGoalAngles.y; bMoved = true; } if ( m_vecCurrentAngles.x > m_vecGoalAngles.x ) { m_vecCurrentAngles.x -= SECURITY_CAMERA_YAW_SPEED; if ( m_vecCurrentAngles.x < m_vecGoalAngles.x ) m_vecCurrentAngles.x = m_vecGoalAngles.x; bMoved = true; } if ( m_vecCurrentAngles.y > m_vecGoalAngles.y ) { m_vecCurrentAngles.y -= SECURITY_CAMERA_YAW_SPEED; if ( m_vecCurrentAngles.y < m_vecGoalAngles.y ) m_vecCurrentAngles.y = m_vecGoalAngles.y; bMoved = true; } if ( bMoved ) { if ( m_pMovementSound ) { CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); controller.SoundChangeVolume( m_pMovementSound, RandomFloat( 0.7f, 0.9f ), 0.05f ); } // Update pitch int iPose = LookupPoseParameter( SECURITY_CAMERA_BC_PITCH ); SetPoseParameter( iPose, m_vecCurrentAngles.x ); // Update yaw iPose = LookupPoseParameter( SECURITY_CAMERA_BC_YAW ); SetPoseParameter( iPose, m_vecCurrentAngles.y ); InvalidateBoneCache(); } else { if ( m_pMovementSound ) { CSoundEnvelopeController &controller = CSoundEnvelopeController::GetController(); controller.SoundChangeVolume( m_pMovementSound, 0.0f, 0.05f ); } } return bMoved; } //----------------------------------------------------------------------------- // Purpose: // Input : *pEntity - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CNPC_SecurityCamera::FVisible( CBaseEntity *pEntity, int traceMask, CBaseEntity **ppBlocker ) { CBaseEntity *pHitEntity = NULL; if ( BaseClass::FVisible( pEntity, traceMask, &pHitEntity ) ) return true; // If we hit something that's okay to hit anyway, still fire if ( pHitEntity && pHitEntity->MyCombatCharacterPointer() ) { if (IRelationType(pHitEntity) == D_HT) return true; } if (ppBlocker) { *ppBlocker = pHitEntity; } return false; } //----------------------------------------------------------------------------- // Purpose: Allows the turret to fire on targets if they're visible //----------------------------------------------------------------------------- void CNPC_SecurityCamera::ActiveThink( void ) { //Allow descended classes a chance to do something before the think function if ( PreThink( TURRET_ACTIVE ) ) return; //Update our think time SetNextThink( gpGlobals->curtime + 0.1f ); CBaseEntity *pEnemy = GetEnemy(); //If we've become inactive, go back to searching if ( m_bActive == false || !pEnemy ) { SetEnemy( NULL ); SetLastSightTime(); SetThink( &CNPC_SecurityCamera::SearchThink ); m_vecGoalAngles = GetAbsAngles(); return; } //Get our shot positions Vector vecMid = EyePosition(); Vector vecMidEnemy = pEnemy->GetAbsOrigin(); //Store off our last seen location UpdateEnemyMemory( pEnemy, vecMidEnemy ); //Look for our current enemy bool bEnemyVisible = pEnemy->IsAlive() && FInViewCone( pEnemy ) && FVisible( pEnemy ); //Calculate dir and dist to enemy Vector vecDirToEnemy = vecMidEnemy - vecMid; float flDistToEnemy = VectorNormalize( vecDirToEnemy ); CProp_Portal *pPortal = NULL; if ( pEnemy->IsAlive() ) { pPortal = FInViewConeThroughPortal( pEnemy ); if ( pPortal && FVisibleThroughPortal( pPortal, pEnemy ) ) { // Translate our target across the portal Vector vecMidEnemyTransformed; UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vecMidEnemy, vecMidEnemyTransformed ); //Calculate dir and dist to enemy Vector vecDirToEnemyTransformed = vecMidEnemyTransformed - vecMid; float flDistToEnemyTransformed = VectorNormalize( vecDirToEnemyTransformed ); // If it's not visible through normal means or the enemy is closer through the portal, use the translated info if ( !bEnemyVisible || flDistToEnemyTransformed < flDistToEnemy ) { bEnemyVisible = true; vecMidEnemy = vecMidEnemyTransformed; vecDirToEnemy = vecDirToEnemyTransformed; flDistToEnemy = flDistToEnemyTransformed; } else { pPortal = NULL; } } else { pPortal = NULL; } } // Add noise to the look position --m_iTicksTillNextNoise; if ( m_iTicksTillNextNoise <= 0 && flDistToEnemy < 256.0f ) { m_vNoisePos.x = RandomFloat( -8.0f, 8.0f ); m_vNoisePos.y = RandomFloat( -8.0f, 8.0f ); m_vNoisePos.z = RandomFloat( 0.0f, 32.0f ); m_iTicksTillNextNoise = RandomInt( 5, 30 ); } //We want to look at the enemy's eyes so we don't jitter Vector vEnemyEyes = pEnemy->EyePosition(); if ( pPortal ) { UTIL_Portal_PointTransform( pPortal->m_hLinkedPortal->MatrixThisToLinked(), vEnemyEyes, vEnemyEyes ); } Vector vecDirToEnemyEyes = ( vEnemyEyes + m_vNoisePos ) - vecMid; VectorNormalize( vecDirToEnemyEyes ); QAngle vecAnglesToEnemy; VectorAngles( vecDirToEnemyEyes, vecAnglesToEnemy ); Vector vForward, vRight, vUp; GetVectors( &vForward, &vRight, &vUp ); vecAnglesToEnemy.x = acosf( vecDirToEnemyEyes.Dot( -vUp ) ) * ( 180.0f / M_PI ); Vector vProjectedDirToEnemyEyes = vecDirToEnemyEyes - vecDirToEnemyEyes.Dot( vUp ) * vUp; VectorNormalize( vProjectedDirToEnemyEyes ); if ( vProjectedDirToEnemyEyes.IsZero() ) vecAnglesToEnemy.y = m_vecGoalAngles.y; else { if ( vProjectedDirToEnemyEyes.Dot( vForward ) > 0.0f ) vecAnglesToEnemy.y = acosf( vProjectedDirToEnemyEyes.Dot( vRight ) ) * ( 180.0f / M_PI ) - 90.0f; else vecAnglesToEnemy.y = -acosf( vProjectedDirToEnemyEyes.Dot( vRight ) ) * ( 180.0f / M_PI ) - 90.0f; } vecAnglesToEnemy.y = AngleNormalize( vecAnglesToEnemy.y ); //Current enemy is not visible if ( ( bEnemyVisible == false ) || ( flDistToEnemy > SECURITY_CAMERA_RANGE ) ) { if ( gpGlobals->curtime > m_flLastSight ) { // Should we look for a new target? ClearEnemyMemory(); SetEnemy( NULL ); SetLastSightTime(); SetThink( &CNPC_SecurityCamera::SearchThink ); m_vecGoalAngles = GetAbsAngles(); return; } bEnemyVisible = false; } //If we can see our enemy, face it if ( bEnemyVisible ) { m_vecGoalAngles.y = vecAnglesToEnemy.y; m_vecGoalAngles.x = vecAnglesToEnemy.x; m_flLastSight = gpGlobals->curtime + 0.5f; } //Turn to face UpdateFacing(); // Update rope positions for ( int iRope = 0; iRope < SECURITY_CAMERA_NUM_ROPES; ++iRope ) { if ( m_hRopes[ iRope ] ) { m_hRopes[ iRope ]->EndpointsChanged(); } } } //----------------------------------------------------------------------------- // Purpose: Target doesn't exist or has eluded us, so search for one //----------------------------------------------------------------------------- void CNPC_SecurityCamera::SearchThink( void ) { //Allow descended classes a chance to do something before the think function if ( PreThink( TURRET_SEARCHING ) ) return; SetNextThink( gpGlobals->curtime + 0.1f ); //If our enemy has died, pick a new enemy if ( ( GetEnemy() != NULL ) && ( GetEnemy()->IsAlive() == false ) ) { SetEnemy( NULL ); } //Acquire the target if ( GetEnemy() == NULL ) { CBaseEntity *pEnemy = NULL; //CBasePlayer *pPlayer = UTIL_PlayerByIndex( 1 ); for( int i = 1; i <= gpGlobals->maxClients; ++i ) { CBasePlayer *pPlayer = UTIL_PlayerByIndex( i ); if ( pPlayer && pPlayer->IsAlive() ) { if ( FInViewCone( pPlayer ) && FVisible( pPlayer ) ) { pEnemy = pPlayer; break; } else { CProp_Portal *pPortal = FInViewConeThroughPortal( pPlayer ); if ( pPortal && FVisibleThroughPortal( pPortal, pPlayer ) ) { pEnemy = pPlayer; break; } } } } if ( pEnemy ) { SetEnemy( pEnemy ); } } //If we've found a target follow it if ( GetEnemy() != NULL ) { m_flLastSight = 0; m_bActive = true; SetThink( &CNPC_SecurityCamera::ActiveThink ); //EmitSound( "NPC_CeilingTurret.Active" ); return; } --m_iTicksTillNextNoise; if ( m_iTicksTillNextNoise <= 0 ) { //Display that we're scanning m_vecGoalAngles.x = RandomFloat( -10.0f, 30.0f ); m_vecGoalAngles.y = RandomFloat( -80.0f, 80.0f ); m_iTicksTillNextNoise = RandomInt( 10, 35 ); } //Turn and ping //UpdateFacing(); Ping(); } //----------------------------------------------------------------------------- // Purpose: Allows a generic think function before the others are called // Input : state - which state the turret is currently in //----------------------------------------------------------------------------- bool CNPC_SecurityCamera::PreThink( turretState_e state ) { CheckPVSCondition(); //Animate StudioFrameAdvance(); //Do not interrupt current think function return false; } //----------------------------------------------------------------------------- // Purpose: Make a pinging noise so the player knows where we are //----------------------------------------------------------------------------- void CNPC_SecurityCamera::Ping( void ) { //See if it's time to ping again if ( m_flPingTime > gpGlobals->curtime ) return; //Ping! //EmitSound( "NPC_CeilingTurret.Ping" ); m_flPingTime = gpGlobals->curtime + SECURITY_CAMERA_PING_TIME; } //----------------------------------------------------------------------------- // Purpose: Toggle the turret's state //----------------------------------------------------------------------------- void CNPC_SecurityCamera::Toggle( void ) { //Toggle the state if ( m_bEnabled ) { Disable(); } else { Enable(); } } //----------------------------------------------------------------------------- // Purpose: Enable the turret and deploy //----------------------------------------------------------------------------- void CNPC_SecurityCamera::Enable( void ) { m_bEnabled = true; // if the turret is flagged as an autoactivate turret, re-enable its ability open self. if ( m_spawnflags & SF_SECURITY_CAMERA_AUTOACTIVATE ) { m_bAutoStart = true; } SetThink( &CNPC_SecurityCamera::Deploy ); SetNextThink( gpGlobals->curtime + 0.05f ); } //----------------------------------------------------------------------------- // Purpose: Retire the turret until enabled again //----------------------------------------------------------------------------- void CNPC_SecurityCamera::Disable( void ) { m_bEnabled = false; m_bAutoStart = false; SetEnemy( NULL ); SetThink( &CNPC_SecurityCamera::Retire ); SetNextThink( gpGlobals->curtime + 0.1f ); } void CNPC_SecurityCamera::RopesOn( void ) { for ( int iRope = 0; iRope < SECURITY_CAMERA_NUM_ROPES; ++iRope ) { // Make a rope if it doesn't exist if ( !m_hRopes[ iRope ] ) { CFmtStr str; int iStartIndex = LookupAttachment( str.sprintf( "Wire%i_A", iRope + 1 ) ); int iEndIndex = LookupAttachment( str.sprintf( "Wire%i_B", iRope + 1 ) ); m_hRopes[ iRope ] = CRopeKeyframe::Create( this, this, iStartIndex, iEndIndex ); if ( m_hRopes[ iRope ] ) { m_hRopes[ iRope ]->m_Width = 0.7; m_hRopes[ iRope ]->m_nSegments = ROPE_MAX_SEGMENTS; m_hRopes[ iRope ]->EnableWind( false ); m_hRopes[ iRope ]->SetupHangDistance( 9.0f ); m_hRopes[ iRope ]->m_bConstrainBetweenEndpoints = true; } } } } void CNPC_SecurityCamera::RopesOff( void ) { for ( int iRope = 0; iRope < SECURITY_CAMERA_NUM_ROPES; ++iRope ) { // Remove rope if it's alive if ( m_hRopes[ iRope ] ) { UTIL_Remove( m_hRopes[ iRope ] ); m_hRopes[ iRope ] = NULL; } } } void CNPC_SecurityCamera::EyeOn( void ) { if ( !m_hEyeGlow ) { // Create our eye sprite m_hEyeGlow = CSprite::SpriteCreate( SECURITY_CAMERA_GLOW_SPRITE, GetLocalOrigin(), false ); if ( !m_hEyeGlow ) return; m_hEyeGlow->SetTransparency( kRenderWorldGlow, 255, 0, 0, 128, kRenderFxNoDissipation ); m_hEyeGlow->SetAttachment( this, LookupAttachment( "light" ) ); m_hEyeGlow->SetScale( 0.3f, 1.0f ); } } void CNPC_SecurityCamera::EyeOff( void ) { if ( m_hEyeGlow != NULL ) { UTIL_Remove( m_hEyeGlow ); m_hEyeGlow = NULL; } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_SecurityCamera::InputToggle( inputdata_t &inputdata ) { Toggle(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_SecurityCamera::InputEnable( inputdata_t &inputdata ) { Enable(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_SecurityCamera::InputDisable( inputdata_t &inputdata ) { Disable(); } void CNPC_SecurityCamera::InputRagdoll( inputdata_t &inputdata ) { if ( !m_bEnabled ) return; // Leave decal on wall (may want to disable this once decal for where cam touches wall is made) Vector vForward; GetVectors( &vForward, NULL, NULL ); trace_t tr; UTIL_TraceLine ( GetAbsOrigin() + 10.0f * vForward, GetAbsOrigin() -60.0f * vForward, MASK_SHOT, this, COLLISION_GROUP_NONE, &tr ); if ( tr.m_pEnt ) UTIL_DecalTrace( &tr, "SecurityCamera.Detachment" ); // Disable it's AI Disable(); SetThink( &CNPC_SecurityCamera::DeathThink ); EyeOff(); // Make it move IPhysicsObject *pPhysics = VPhysicsGetObject(); if ( !pPhysics || pPhysics->IsMotionEnabled() ) return; pPhysics->EnableMotion( true ); pPhysics->Wake(); PlayDismountSounds(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CNPC_SecurityCamera::DeathThink( void ) { if ( PreThink( TURRET_DEAD ) ) return; // Level out our angles m_vecGoalAngles.x = 120.0f; m_vecGoalAngles.y = 0.0f; SetNextThink( gpGlobals->curtime + 0.1f ); if ( m_lifeState != LIFE_DEAD ) { m_lifeState = LIFE_DEAD; //EmitSound( "NPC_CeilingTurret.Die" ); } // lots of smoke Vector pos; CollisionProp()->RandomPointInBounds( vec3_origin, Vector( 1, 1, 1 ), &pos ); CBroadcastRecipientFilter filter; te->Smoke( filter, 0.0, &pos, g_sModelIndexSmoke, 2.5, 10 ); g_pEffects->Sparks( pos ); if ( !UpdateFacing() ) { m_flPlaybackRate = 0; SetThink( NULL ); } } //----------------------------------------------------------------------------- // Purpose: // Input : *pEnemy - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CNPC_SecurityCamera::CanBeAnEnemyOf( CBaseEntity *pEnemy ) { // If we're out of ammo, make friendly companions ignore us if ( m_spawnflags & SF_SECURITY_CAMERA_OUT_OF_AMMO ) { if ( pEnemy->Classify() == CLASS_PLAYER_ALLY_VITAL ) return false; } return BaseClass::CanBeAnEnemyOf( pEnemy ); } void PlayDismountSounds() { // Play GLaDOS's audio reaction CPortal_Player* pPlayer = ToPortalPlayer( UTIL_PlayerByIndex( 1 ) ); CAI_BaseActor* pGlaDOS = (CAI_BaseActor*)gEntList.FindEntityByName( NULL, "Aperture_AI" ); if ( !pPlayer || !pGlaDOS ) { DevMsg( 2, "Could not play CNPC_SecurityCamera dismount scene, make sure actor named 'Aperture_AI' is present in map.\n" ); return; } IGameEvent *event = gameeventmanager->CreateEvent( "security_camera_detached" ); if ( event ) { gameeventmanager->FireEvent( event ); } // If glados is currently talking, don't let her talk over herself or interrupt a potentially important speech. // Should we play the dismount sound after she's done? or is that too disjointed from the camera dismounting act to make sense... if ( IsRunningScriptedScene( pGlaDOS, false ) ) { return; } pPlayer->IncNumCamerasDetatched(); int iNumCamerasDetatched = pPlayer->GetNumCamerasDetatched(); // If they've knocked down every one possible, play special '1' sound. if ( iNumCamerasDetatched == SECURITY_CAMERA_TOTAL_TO_KNOCK_DOWN ) { InstancedScriptedScene( pGlaDOS, CAMERA_DESTROYED_SCENE_1 ); } else // iNumCamerasDetatched < SECURITY_CAMERA_TOTAL_TO_KNOCK_DOWN { // Play different sounds based on progress towards security camera knockdown total. switch ( iNumCamerasDetatched ) { case 1: InstancedScriptedScene( pGlaDOS, CAMERA_DESTROYED_SCENE_2 ); break; case 2: InstancedScriptedScene( pGlaDOS, CAMERA_DESTROYED_SCENE_3 ); break; case 3: InstancedScriptedScene( pGlaDOS, CAMERA_DESTROYED_SCENE_4 ); break; default: InstancedScriptedScene( pGlaDOS, CAMERA_DESTROYED_SCENE_5 ); break; } } }