You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1167 lines
30 KiB
1167 lines
30 KiB
//========= 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; |
|
} |
|
} |
|
} |