diff options
Diffstat (limited to 'game/server/portal/npc_security_camera.cpp')
| -rw-r--r-- | game/server/portal/npc_security_camera.cpp | 1167 |
1 files changed, 1167 insertions, 0 deletions
diff --git a/game/server/portal/npc_security_camera.cpp b/game/server/portal/npc_security_camera.cpp new file mode 100644 index 0000000..1c06a0a --- /dev/null +++ b/game/server/portal/npc_security_camera.cpp @@ -0,0 +1,1167 @@ +//========= 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<CAI_BaseNPC>, public CDefaultPlayerPickupVPhysics +{ + DECLARE_CLASS( CNPC_SecurityCamera, CNPCBaseInteractive<CAI_BaseNPC> ); +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<CRopeKeyframe> m_hRopes[ SECURITY_CAMERA_NUM_ROPES ]; + CHandle<CSprite> 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; + } + } +}
\ No newline at end of file |