aboutsummaryrefslogtreecommitdiff
path: root/mp/src/game/server/hl2/npc_combinecamera.cpp
diff options
context:
space:
mode:
authorJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
committerJoe Ludwig <[email protected]>2013-06-26 15:22:04 -0700
commit39ed87570bdb2f86969d4be821c94b722dc71179 (patch)
treeabc53757f75f40c80278e87650ea92808274aa59 /mp/src/game/server/hl2/npc_combinecamera.cpp
downloadsource-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.tar.xz
source-sdk-2013-39ed87570bdb2f86969d4be821c94b722dc71179.zip
First version of the SOurce SDK 2013
Diffstat (limited to 'mp/src/game/server/hl2/npc_combinecamera.cpp')
-rw-r--r--mp/src/game/server/hl2/npc_combinecamera.cpp1188
1 files changed, 1188 insertions, 0 deletions
diff --git a/mp/src/game/server/hl2/npc_combinecamera.cpp b/mp/src/game/server/hl2/npc_combinecamera.cpp
new file mode 100644
index 00000000..5ac48e57
--- /dev/null
+++ b/mp/src/game/server/hl2/npc_combinecamera.cpp
@@ -0,0 +1,1188 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose: Security cameras will track a default target (if they have one)
+// until they either acquire an enemy to track or are told to track
+// an entity via an input. If they lose their target they will
+// revert to tracking their default target. They acquire enemies
+// using the relationship table just like any other NPC.
+//
+// Cameras have two zones of awareness, an inner zone formed by the
+// intersection of an inner FOV and an inner radius. The camera is
+// fully aware of entities in the inner zone and will acquire enemies
+// seen there.
+//
+// The outer zone of awareness is formed by the intersection of an
+// outer FOV and an outer radius. The camera is only vaguely aware
+// of entities in the outer zone and will flash amber when enemies
+// are there, but will otherwise ignore them.
+//
+// They can be made angry via an input, at which time they sound an
+// alarm and snap a few pictures of whatever they are tracking. They
+// can also be set to become angry anytime they acquire an enemy.
+//
+//=============================================================================//
+
+#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"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+// Debug visualization
+ConVar g_debug_combine_camera("g_debug_combine_camera", "0");
+
+#define COMBINE_CAMERA_MODEL "models/combine_camera/combine_camera.mdl"
+#define COMBINE_CAMERA_GLOW_SPRITE "sprites/glow1.vmt"
+#define COMBINE_CAMERA_FLASH_SPRITE "sprites/light_glow03.vmt"
+#define COMBINE_CAMERA_BC_YAW "aim_yaw"
+#define COMBINE_CAMERA_BC_PITCH "aim_pitch"
+
+#define COMBINE_CAMERA_SPREAD VECTOR_CONE_2DEGREES
+#define COMBINE_CAMERA_MAX_WAIT 5
+#define COMBINE_CAMERA_PING_TIME 1.0f
+
+// Spawnflags
+#define SF_COMBINE_CAMERA_BECOMEANGRY 0x00000020
+#define SF_COMBINE_CAMERA_IGNOREENEMIES 0x00000040
+#define SF_COMBINE_CAMERA_STARTINACTIVE 0x00000080
+
+// Heights
+#define COMBINE_CAMERA_RETRACT_HEIGHT 24
+#define COMBINE_CAMERA_DEPLOY_HEIGHT 64
+
+
+// Activities
+int ACT_COMBINE_CAMERA_OPEN;
+int ACT_COMBINE_CAMERA_CLOSE;
+int ACT_COMBINE_CAMERA_OPEN_IDLE;
+int ACT_COMBINE_CAMERA_CLOSED_IDLE;
+int ACT_COMBINE_CAMERA_FIRE;
+
+
+const float CAMERA_CLICK_INTERVAL = 0.5f;
+const float CAMERA_MOVE_INTERVAL = 1.0f;
+
+
+//
+// The camera has two FOVs - a wide one for becoming slightly aware of someone,
+// a narrow one for becoming totally aware of them.
+//
+const float CAMERA_FOV_WIDE = 0.5;
+const float CAMERA_FOV_NARROW = 0.707;
+
+
+// Camera states
+enum cameraState_e
+{
+ CAMERA_SEARCHING,
+ CAMERA_AUTO_SEARCHING,
+ CAMERA_ACTIVE,
+ CAMERA_DEAD,
+};
+
+
+// Eye states
+enum eyeState_t
+{
+ CAMERA_EYE_IDLE, // Nothing abnormal in the inner or outer viewcone, dim green.
+ CAMERA_EYE_SEEKING_TARGET, // Something in the outer viewcone, flashes amber as it converges on the target.
+ CAMERA_EYE_FOUND_TARGET, // Something in the inner viewcone, bright amber.
+ CAMERA_EYE_ANGRY, // Found a target that we don't like: angry, bright red.
+ CAMERA_EYE_DORMANT, // Not active
+ CAMERA_EYE_DEAD, // Completely invisible
+ CAMERA_EYE_DISABLED, // Turned off, must be reactivated before it'll deploy again (completely invisible)
+ CAMERA_EYE_HAPPY, // Found a target that we like: go green for a second
+};
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+class CNPC_CombineCamera : public CAI_BaseNPC
+{
+ DECLARE_CLASS(CNPC_CombineCamera, CAI_BaseNPC);
+public:
+
+ CNPC_CombineCamera();
+ ~CNPC_CombineCamera();
+
+ void Precache();
+ void Spawn();
+ Vector HeadDirection2D();
+
+ int DrawDebugTextOverlays();
+
+ void Deploy();
+ void ActiveThink();
+ void SearchThink();
+ void DeathThink();
+
+ void InputToggle(inputdata_t &inputdata);
+ void InputEnable(inputdata_t &inputdata);
+ void InputDisable(inputdata_t &inputdata);
+ void InputSetAngry(inputdata_t &inputdata);
+ void InputSetIdle(inputdata_t &inputdata);
+
+ void DrawDebugGeometryOverlays(void);
+
+ float MaxYawSpeed();
+
+ int OnTakeDamage(const CTakeDamageInfo &inputInfo);
+
+ Class_T Classify() { return (m_bEnabled) ? CLASS_MILITARY : CLASS_NONE; }
+
+ bool IsValidEnemy( CBaseEntity *pEnemy );
+ bool FVisible(CBaseEntity *pEntity, int traceMask = MASK_BLOCKLOS, CBaseEntity **ppBlocker = NULL);
+
+ Vector EyeOffset(Activity nActivity)
+ {
+ Vector vecEyeOffset(0,0,-64);
+ GetEyePosition(GetModelPtr(), vecEyeOffset);
+ return vecEyeOffset;
+ }
+
+ Vector EyePosition()
+ {
+ return GetAbsOrigin() + EyeOffset(GetActivity());
+ }
+
+protected:
+
+ CBaseEntity *GetTarget();
+ bool UpdateFacing();
+ void TrackTarget(CBaseEntity *pTarget);
+
+ bool PreThink(cameraState_e state);
+ void SetEyeState(eyeState_t state);
+ void MaintainEye();
+ void Ping();
+ void Toggle();
+ void Enable();
+ void Disable();
+ void SetHeight(float height);
+
+ CBaseEntity *MaintainEnemy();
+ void SetAngry(bool bAngry);
+
+protected:
+ int m_iAmmoType;
+ int m_iMinHealthDmg;
+
+ int m_nInnerRadius; // The camera will only lock onto enemies that are within the inner radius.
+ int m_nOuterRadius; // The camera will flash amber when enemies are within the outer radius, but outside the inner radius.
+
+ bool m_bActive; // The camera is deployed and looking for targets
+ bool m_bAngry; // The camera has gotten angry at someone and sounded an alarm.
+ bool m_bBlinkState;
+ bool m_bEnabled; // Denotes whether the camera is able to deploy or not
+
+ string_t m_sDefaultTarget;
+
+ EHANDLE m_hEnemyTarget; // Entity we acquired as an enemy.
+
+ float m_flPingTime;
+ float m_flClickTime; // Time to take next picture while angry.
+ int m_nClickCount; // Counts pictures taken since we last became angry.
+ float m_flMoveSoundTime;
+ float m_flTurnOffEyeFlashTime;
+ float m_flEyeHappyTime;
+
+ QAngle m_vecGoalAngles;
+
+ CSprite *m_pEyeGlow;
+ CSprite *m_pEyeFlash;
+
+ DECLARE_DATADESC();
+};
+
+
+BEGIN_DATADESC(CNPC_CombineCamera)
+
+ DEFINE_FIELD(m_iAmmoType, FIELD_INTEGER),
+ DEFINE_KEYFIELD(m_iMinHealthDmg, FIELD_INTEGER, "minhealthdmg"),
+ DEFINE_KEYFIELD(m_nInnerRadius, FIELD_INTEGER, "innerradius"),
+ DEFINE_KEYFIELD(m_nOuterRadius, FIELD_INTEGER, "outerradius"),
+ DEFINE_FIELD(m_bActive, FIELD_BOOLEAN),
+ DEFINE_FIELD(m_bAngry, FIELD_BOOLEAN),
+ DEFINE_FIELD(m_bBlinkState, FIELD_BOOLEAN),
+ DEFINE_FIELD(m_bEnabled, FIELD_BOOLEAN),
+ DEFINE_KEYFIELD(m_sDefaultTarget, FIELD_STRING, "defaulttarget"),
+ DEFINE_FIELD(m_hEnemyTarget, FIELD_EHANDLE),
+ DEFINE_FIELD(m_flPingTime, FIELD_TIME),
+ DEFINE_FIELD(m_flClickTime, FIELD_TIME),
+ DEFINE_FIELD(m_nClickCount, FIELD_INTEGER ),
+ DEFINE_FIELD(m_flMoveSoundTime, FIELD_TIME),
+ DEFINE_FIELD(m_flTurnOffEyeFlashTime, FIELD_TIME),
+ DEFINE_FIELD(m_flEyeHappyTime, FIELD_TIME),
+ DEFINE_FIELD(m_vecGoalAngles, FIELD_VECTOR),
+ DEFINE_FIELD(m_pEyeGlow, FIELD_CLASSPTR),
+ DEFINE_FIELD(m_pEyeFlash, FIELD_CLASSPTR),
+
+ 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, "SetAngry", InputSetAngry),
+ DEFINE_INPUTFUNC(FIELD_VOID, "SetIdle", InputSetIdle),
+
+END_DATADESC()
+
+LINK_ENTITY_TO_CLASS(npc_combine_camera, CNPC_CombineCamera);
+
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CNPC_CombineCamera::CNPC_CombineCamera()
+{
+ m_bActive = false;
+ m_pEyeGlow = NULL;
+ m_pEyeFlash = NULL;
+ m_iAmmoType = -1;
+ m_iMinHealthDmg = 0;
+ m_flPingTime = 0;
+ m_bBlinkState = false;
+ m_bEnabled = false;
+
+ m_vecGoalAngles.Init();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CNPC_CombineCamera::~CNPC_CombineCamera()
+{
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Precache
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::Precache()
+{
+ PrecacheModel(COMBINE_CAMERA_MODEL);
+ PrecacheModel(COMBINE_CAMERA_GLOW_SPRITE);
+ PrecacheModel(COMBINE_CAMERA_FLASH_SPRITE);
+
+ // Activities
+ ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_OPEN);
+ ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_CLOSE);
+ ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_CLOSED_IDLE);
+ ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_OPEN_IDLE);
+ ADD_CUSTOM_ACTIVITY(CNPC_CombineCamera, ACT_COMBINE_CAMERA_FIRE);
+
+ PrecacheScriptSound( "NPC_CombineCamera.Move" );
+ PrecacheScriptSound( "NPC_CombineCamera.BecomeIdle" );
+ PrecacheScriptSound( "NPC_CombineCamera.Active" );
+ PrecacheScriptSound( "NPC_CombineCamera.Click" );
+ PrecacheScriptSound( "NPC_CombineCamera.Ping" );
+ PrecacheScriptSound( "NPC_CombineCamera.Angry" );
+ PrecacheScriptSound( "NPC_CombineCamera.Die" );
+
+ BaseClass::Precache();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Spawn the entity
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::Spawn()
+{
+ Precache();
+
+ SetModel(COMBINE_CAMERA_MODEL);
+
+ m_pEyeFlash = CSprite::SpriteCreate(COMBINE_CAMERA_FLASH_SPRITE, GetLocalOrigin(), FALSE);
+ m_pEyeFlash->SetTransparency(kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation);
+ m_pEyeFlash->SetAttachment(this, 2);
+ m_pEyeFlash->SetBrightness(0);
+ m_pEyeFlash->SetScale(1.0);
+
+ BaseClass::Spawn();
+
+ m_HackedGunPos = Vector(0, 0, 12.75);
+ SetViewOffset(EyeOffset(ACT_IDLE));
+ m_flFieldOfView = CAMERA_FOV_WIDE;
+ m_takedamage = DAMAGE_YES;
+ m_iHealth = 50;
+ m_bloodColor = BLOOD_COLOR_MECH;
+
+ SetSolid(SOLID_BBOX);
+ AddSolidFlags(FSOLID_NOT_STANDABLE);
+
+ SetHeight(COMBINE_CAMERA_RETRACT_HEIGHT);
+
+ AddFlag(FL_AIMTARGET);
+
+ SetPoseParameter(COMBINE_CAMERA_BC_YAW, 0);
+ SetPoseParameter(COMBINE_CAMERA_BC_PITCH, 0);
+
+ m_iAmmoType = GetAmmoDef()->Index("Pistol");
+
+ // Create our eye sprite
+ m_pEyeGlow = CSprite::SpriteCreate(COMBINE_CAMERA_GLOW_SPRITE, GetLocalOrigin(), false);
+ m_pEyeGlow->SetTransparency(kRenderWorldGlow, 255, 0, 0, 128, kRenderFxNoDissipation);
+ m_pEyeGlow->SetAttachment(this, 2);
+
+ // Set our enabled state
+ m_bEnabled = ((m_spawnflags & SF_COMBINE_CAMERA_STARTINACTIVE) == false);
+
+ // Make sure the radii are sane.
+ if (m_nOuterRadius <= 0)
+ {
+ m_nOuterRadius = 300;
+ }
+
+ if (m_nInnerRadius <= 0)
+ {
+ m_nInnerRadius = 450;
+ }
+
+ if (m_nOuterRadius < m_nInnerRadius)
+ {
+ V_swap(m_nOuterRadius, m_nInnerRadius);
+ }
+
+ // Do we start active?
+ if (m_bEnabled)
+ {
+ Deploy();
+ }
+ else
+ {
+ SetEyeState(CAMERA_EYE_DISABLED);
+ }
+
+ //Adrian: No shadows on these guys.
+ AddEffects( EF_NOSHADOW );
+
+ // Stagger our starting times
+ SetNextThink( gpGlobals->curtime + random->RandomFloat(0.1f, 0.3f) );
+
+ // Don't allow us to skip animation setup because our attachments are critical to us!
+ SetBoneCacheFlags( BCF_NO_ANIMATION_SKIP );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+CBaseEntity *CNPC_CombineCamera::GetTarget()
+{
+ return m_hEnemyTarget;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+int CNPC_CombineCamera::OnTakeDamage(const CTakeDamageInfo &inputInfo)
+{
+ if (!m_takedamage)
+ return 0;
+
+ CTakeDamageInfo info = inputInfo;
+
+ if (m_bActive == false)
+ info.ScaleDamage(0.1f);
+
+ // If attacker can't do at least the min required damage to us, don't take any damage from them
+ if (info.GetDamage() < m_iMinHealthDmg)
+ return 0;
+
+ 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???
+
+ // FIXME: This needs to throw a ragdoll gib or something other than animating the retraction -- jdw
+
+ ExplosionCreate(GetAbsOrigin(), GetLocalAngles(), this, 100, 100, false);
+ SetThink(&CNPC_CombineCamera::DeathThink);
+
+ StopSound("Alert");
+
+ m_OnDamaged.FireOutput(info.GetInflictor(), this);
+
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ return 0;
+ }
+
+ return 1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Deploy and start searching for targets.
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::Deploy()
+{
+ m_vecGoalAngles = GetAbsAngles();
+
+ SetNextThink( gpGlobals->curtime );
+
+ SetEyeState(CAMERA_EYE_IDLE);
+ m_bActive = true;
+
+ SetHeight(COMBINE_CAMERA_DEPLOY_HEIGHT);
+ SetIdealActivity((Activity) ACT_COMBINE_CAMERA_OPEN_IDLE);
+ m_flPlaybackRate = 0;
+ SetThink(&CNPC_CombineCamera::SearchThink);
+
+ EmitSound("NPC_CombineCamera.Move");
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the speed at which the camera can face a target
+//-----------------------------------------------------------------------------
+float CNPC_CombineCamera::MaxYawSpeed()
+{
+ if (m_hEnemyTarget)
+ return 180.0f;
+
+ return 60.0f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Causes the camera to face its desired angles
+//-----------------------------------------------------------------------------
+bool CNPC_CombineCamera::UpdateFacing()
+{
+ bool bMoved = false;
+ matrix3x4_t localToWorld;
+
+ GetAttachment(LookupAttachment("eyes"), localToWorld);
+
+ Vector vecGoalDir;
+ AngleVectors(m_vecGoalAngles, &vecGoalDir );
+
+ Vector vecGoalLocalDir;
+ VectorIRotate(vecGoalDir, localToWorld, vecGoalLocalDir);
+
+ QAngle vecGoalLocalAngles;
+ VectorAngles(vecGoalLocalDir, vecGoalLocalAngles);
+
+ // Update pitch
+ float flDiff = AngleNormalize(UTIL_ApproachAngle( vecGoalLocalAngles.x, 0.0, 0.1f * MaxYawSpeed()));
+
+ int iPose = LookupPoseParameter(COMBINE_CAMERA_BC_PITCH);
+ SetPoseParameter(iPose, GetPoseParameter(iPose) + (flDiff / 1.5f));
+
+ if (fabs(flDiff) > 0.1f)
+ {
+ bMoved = true;
+ }
+
+ // Update yaw
+ flDiff = AngleNormalize(UTIL_ApproachAngle( vecGoalLocalAngles.y, 0.0, 0.1f * MaxYawSpeed()));
+
+ iPose = LookupPoseParameter(COMBINE_CAMERA_BC_YAW);
+ SetPoseParameter(iPose, GetPoseParameter(iPose) + (flDiff / 1.5f));
+
+ if (fabs(flDiff) > 0.1f)
+ {
+ bMoved = true;
+ }
+
+ if (bMoved && (m_flMoveSoundTime < gpGlobals->curtime))
+ {
+ EmitSound("NPC_CombineCamera.Move");
+ m_flMoveSoundTime = gpGlobals->curtime + CAMERA_MOVE_INTERVAL;
+ }
+
+ // You're going to make decisions based on this info. So bump the bone cache after you calculate everything
+ InvalidateBoneCache();
+
+ return bMoved;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+Vector CNPC_CombineCamera::HeadDirection2D()
+{
+ Vector vecMuzzle, vecMuzzleDir;
+
+ GetAttachment("eyes", vecMuzzle, &vecMuzzleDir );
+ vecMuzzleDir.z = 0;
+ VectorNormalize(vecMuzzleDir);
+
+ return vecMuzzleDir;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pEntity -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CNPC_CombineCamera::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: Enemies are only valid if they're inside our radius
+//-----------------------------------------------------------------------------
+bool CNPC_CombineCamera::IsValidEnemy( CBaseEntity *pEnemy )
+{
+ Vector vecDelta = pEnemy->GetAbsOrigin() - GetAbsOrigin();
+ float flDist = vecDelta.Length();
+ if ( (flDist > m_nOuterRadius) || !FInViewCone(pEnemy) )
+ return false;
+
+ return BaseClass::IsValidEnemy( pEnemy );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Called when we have no scripted target. Looks for new enemies to track.
+//-----------------------------------------------------------------------------
+CBaseEntity *CNPC_CombineCamera::MaintainEnemy()
+{
+ if (HasSpawnFlags(SF_COMBINE_CAMERA_IGNOREENEMIES))
+ return NULL;
+
+ GetSenses()->Look(m_nOuterRadius);
+
+ CBaseEntity *pEnemy = BestEnemy();
+ if (pEnemy)
+ {
+ // See if our best enemy is too far away to care about.
+ Vector vecDelta = pEnemy->GetAbsOrigin() - GetAbsOrigin();
+ float flDist = vecDelta.Length();
+ if (flDist < m_nOuterRadius)
+ {
+ if (FInViewCone(pEnemy))
+ {
+ // dvs: HACK: for checking multiple view cones
+ float flSaveFieldOfView = m_flFieldOfView;
+ m_flFieldOfView = CAMERA_FOV_NARROW;
+
+ // Is the target visible?
+ bool bVisible = FVisible(pEnemy);
+ m_flFieldOfView = flSaveFieldOfView;
+ if ( bVisible )
+ return pEnemy;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Think while actively tracking a target.
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::ActiveThink()
+{
+ // Allow descended classes a chance to do something before the think function
+ if (PreThink(CAMERA_ACTIVE))
+ return;
+
+ // No active target, look for suspicious characters.
+ CBaseEntity *pTarget = MaintainEnemy();
+ if ( !pTarget )
+ {
+ // Nobody suspicious. Go back to being idle.
+ m_hEnemyTarget = NULL;
+ EmitSound("NPC_CombineCamera.BecomeIdle");
+ SetAngry(false);
+ SetThink(&CNPC_CombineCamera::SearchThink);
+ SetNextThink( gpGlobals->curtime );
+ return;
+ }
+
+ // Examine the target until it reaches our inner radius
+ if ( pTarget != m_hEnemyTarget )
+ {
+ Vector vecDelta = pTarget->GetAbsOrigin() - GetAbsOrigin();
+ float flDist = vecDelta.Length();
+ if ( (flDist < m_nInnerRadius) && FInViewCone(pTarget) )
+ {
+ m_OnFoundEnemy.Set(pTarget, pTarget, this);
+
+ // If it's a citizen, it's ok. If it's the player, it's not ok.
+ if ( pTarget->IsPlayer() )
+ {
+ SetEyeState(CAMERA_EYE_FOUND_TARGET);
+
+ if (HasSpawnFlags(SF_COMBINE_CAMERA_BECOMEANGRY))
+ {
+ SetAngry(true);
+ }
+ else
+ {
+ EmitSound("NPC_CombineCamera.Active");
+ }
+
+ m_OnFoundPlayer.Set(pTarget, pTarget, this);
+ m_hEnemyTarget = pTarget;
+ }
+ else
+ {
+ SetEyeState(CAMERA_EYE_HAPPY);
+ m_flEyeHappyTime = gpGlobals->curtime + 2.0;
+
+ // Now forget about this target forever
+ AddEntityRelationship( pTarget, D_NU, 99 );
+ }
+ }
+ else
+ {
+ // If we get angry automatically, we get un-angry automatically
+ if ( HasSpawnFlags(SF_COMBINE_CAMERA_BECOMEANGRY) && m_bAngry )
+ {
+ SetAngry(false);
+ }
+ m_hEnemyTarget = NULL;
+
+ // We don't quite see this guy, but we sense him.
+ SetEyeState(CAMERA_EYE_SEEKING_TARGET);
+ }
+ }
+
+ // Update our think time
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ TrackTarget(pTarget);
+ MaintainEye();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pTarget -
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::TrackTarget( CBaseEntity *pTarget )
+{
+ if (!pTarget)
+ return;
+
+ // Calculate direction to target
+ Vector vecMid = EyePosition();
+ Vector vecMidTarget = pTarget->BodyTarget(vecMid);
+ Vector vecDirToTarget = vecMidTarget - vecMid;
+
+ // We want to look at the target's eyes so we don't jitter
+ Vector vecDirToTargetEyes = pTarget->WorldSpaceCenter() - vecMid;
+ VectorNormalize(vecDirToTargetEyes);
+
+ QAngle vecAnglesToTarget;
+ VectorAngles(vecDirToTargetEyes, vecAnglesToTarget);
+
+ // Draw debug info
+ if (g_debug_combine_camera.GetBool())
+ {
+ NDebugOverlay::Cross3D(vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05);
+ NDebugOverlay::Cross3D(pTarget->WorldSpaceCenter(), -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05);
+ NDebugOverlay::Line(vecMid, pTarget->WorldSpaceCenter(), 0, 255, 0, false, 0.05);
+
+ NDebugOverlay::Cross3D(vecMid, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05);
+ NDebugOverlay::Cross3D(vecMidTarget, -Vector(2,2,2), Vector(2,2,2), 0, 255, 0, false, 0.05);
+ NDebugOverlay::Line(vecMid, vecMidTarget, 0, 255, 0, false, 0.05f);
+ }
+
+ Vector vecMuzzle, vecMuzzleDir;
+ QAngle vecMuzzleAng;
+
+ GetAttachment("eyes", vecMuzzle, &vecMuzzleDir);
+
+ SetIdealActivity((Activity) ACT_COMBINE_CAMERA_OPEN_IDLE);
+
+ m_vecGoalAngles.y = vecAnglesToTarget.y;
+ m_vecGoalAngles.x = vecAnglesToTarget.x;
+
+ // Turn to face
+ UpdateFacing();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::MaintainEye()
+{
+ // Angry cameras take a few pictures of their target.
+ if ((m_bAngry) && (m_nClickCount <= 3))
+ {
+ if ((m_flClickTime != 0) && (m_flClickTime < gpGlobals->curtime))
+ {
+ m_pEyeFlash->SetScale(1.0);
+ m_pEyeFlash->SetBrightness(255);
+ m_pEyeFlash->SetColor(255,255,255);
+
+ EmitSound("NPC_CombineCamera.Click");
+
+ m_flTurnOffEyeFlashTime = gpGlobals->curtime + 0.1;
+ m_flClickTime = gpGlobals->curtime + CAMERA_CLICK_INTERVAL;
+ }
+ else if ((m_flTurnOffEyeFlashTime != 0) && (m_flTurnOffEyeFlashTime < gpGlobals->curtime))
+ {
+ m_flTurnOffEyeFlashTime = 0;
+ m_pEyeFlash->SetBrightness( 0, 0.25f );
+ m_nClickCount++;
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Target doesn't exist or has eluded us, so search for one
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::SearchThink()
+{
+ // Allow descended classes a chance to do something before the think function
+ if (PreThink(CAMERA_SEARCHING))
+ return;
+
+ SetNextThink( gpGlobals->curtime + 0.05f );
+
+ SetIdealActivity((Activity) ACT_COMBINE_CAMERA_OPEN_IDLE);
+
+ if ( !GetTarget() )
+ {
+ // Try to acquire a new target
+ if (MaintainEnemy())
+ {
+ SetThink( &CNPC_CombineCamera::ActiveThink );
+ return;
+ }
+ }
+
+ // Display that we're scanning
+ m_vecGoalAngles.x = 15.0f;
+ m_vecGoalAngles.y = GetAbsAngles().y + (sin(gpGlobals->curtime * 2.0f) * 45.0f);
+
+ // Turn and ping
+ UpdateFacing();
+ Ping();
+
+ SetEyeState(CAMERA_EYE_IDLE);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows a generic think function before the others are called
+// Input : state - which state the camera is currently in
+//-----------------------------------------------------------------------------
+bool CNPC_CombineCamera::PreThink(cameraState_e state)
+{
+ CheckPVSCondition();
+
+ MaintainActivity();
+ StudioFrameAdvance();
+
+ // If we're disabled, shut down
+ if ( !m_bEnabled )
+ {
+ SetIdealActivity((Activity) ACT_COMBINE_CAMERA_CLOSED_IDLE);
+ SetNextThink( gpGlobals->curtime + 0.1f );
+ return true;
+ }
+
+ // Do not interrupt current think function
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the state of the glowing eye attached to the camera
+// Input : state - state the eye should be in
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::SetEyeState(eyeState_t state)
+{
+ // Must have a valid eye to affect
+ if (m_pEyeGlow == NULL)
+ return;
+
+ if (m_bAngry)
+ {
+ m_pEyeGlow->SetColor(255, 0, 0);
+ m_pEyeGlow->SetBrightness(164, 0.1f);
+ m_pEyeGlow->SetScale(0.4f, 0.1f);
+ return;
+ }
+
+ // If we're switching to IDLE, and we're still happy, use happy instead
+ if ( state == CAMERA_EYE_IDLE && m_flEyeHappyTime > gpGlobals->curtime )
+ {
+ state = CAMERA_EYE_HAPPY;
+ }
+
+ // Set the state
+ switch (state)
+ {
+ default:
+ case CAMERA_EYE_IDLE:
+ {
+ m_pEyeGlow->SetColor(0, 255, 0);
+ m_pEyeGlow->SetBrightness(164, 0.1f);
+ m_pEyeGlow->SetScale(0.4f, 0.1f);
+ break;
+ }
+
+ case CAMERA_EYE_SEEKING_TARGET:
+ {
+ // Toggle our state
+ m_bBlinkState = !m_bBlinkState;
+
+ // Amber
+ m_pEyeGlow->SetColor(255, 128, 0);
+
+ if (m_bBlinkState)
+ {
+ // Fade up and scale up
+ m_pEyeGlow->SetScale(0.25f, 0.1f);
+ m_pEyeGlow->SetBrightness(164, 0.1f);
+ }
+ else
+ {
+ // Fade down and scale down
+ m_pEyeGlow->SetScale(0.2f, 0.1f);
+ m_pEyeGlow->SetBrightness(64, 0.1f);
+ }
+
+ break;
+ }
+
+ case CAMERA_EYE_FOUND_TARGET:
+ {
+ if (!m_bAngry)
+ {
+ // Amber
+ m_pEyeGlow->SetColor(255, 128, 0);
+
+ // Fade up and scale up
+ m_pEyeGlow->SetScale(0.45f, 0.1f);
+ m_pEyeGlow->SetBrightness(220, 0.1f);
+ }
+ else
+ {
+ m_pEyeGlow->SetColor(255, 0, 0);
+ m_pEyeGlow->SetBrightness(164, 0.1f);
+ m_pEyeGlow->SetScale(0.4f, 0.1f);
+ }
+
+ break;
+ }
+
+ case CAMERA_EYE_DORMANT: // Fade out and scale down
+ {
+ m_pEyeGlow->SetColor(0, 255, 0);
+ m_pEyeGlow->SetScale(0.1f, 0.5f);
+ m_pEyeGlow->SetBrightness(64, 0.5f);
+ break;
+ }
+
+ case CAMERA_EYE_DEAD: // Fade out slowly
+ {
+ m_pEyeGlow->SetColor(255, 0, 0);
+ m_pEyeGlow->SetScale(0.1f, 3.0f);
+ m_pEyeGlow->SetBrightness(0, 3.0f);
+ break;
+ }
+
+ case CAMERA_EYE_DISABLED:
+ {
+ m_pEyeGlow->SetColor(0, 255, 0);
+ m_pEyeGlow->SetScale(0.1f, 1.0f);
+ m_pEyeGlow->SetBrightness(0, 1.0f);
+ break;
+ }
+
+ case CAMERA_EYE_HAPPY:
+ {
+ m_pEyeGlow->SetColor(0, 255, 0);
+ m_pEyeGlow->SetBrightness(255, 0.1f);
+ m_pEyeGlow->SetScale(0.5f, 0.1f);
+ break;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Make a pinging noise so the player knows where we are
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::Ping()
+{
+ // See if it's time to ping again
+ if (m_flPingTime > gpGlobals->curtime)
+ return;
+
+ // Ping!
+ EmitSound("NPC_CombineCamera.Ping");
+ m_flPingTime = gpGlobals->curtime + COMBINE_CAMERA_PING_TIME;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Toggle the camera's state
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::Toggle()
+{
+ if (m_bEnabled)
+ {
+ Disable();
+ }
+ else
+ {
+ Enable();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Enable the camera and deploy
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::Enable()
+{
+ m_bEnabled = true;
+ SetThink(&CNPC_CombineCamera::Deploy);
+ SetNextThink( gpGlobals->curtime + 0.05f );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Retire the camera until enabled again
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::Disable()
+{
+ m_bEnabled = false;
+ m_hEnemyTarget = NULL;
+ SetNextThink( gpGlobals->curtime + 0.1f );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Toggle the camera's state via input function
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::InputToggle(inputdata_t &inputdata)
+{
+ Toggle();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler to enable the camera.
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::InputEnable(inputdata_t &inputdata)
+{
+ Enable();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Input handler to disable the camera.
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::InputDisable(inputdata_t &inputdata)
+{
+ Disable();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: When we become angry, we make an angry sound and start photographing
+// whatever target we are tracking.
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::SetAngry(bool bAngry)
+{
+ if ((bAngry) && (!m_bAngry))
+ {
+ m_bAngry = true;
+ m_nClickCount = 0;
+ m_flClickTime = gpGlobals->curtime + 0.4;
+ EmitSound("NPC_CombineCamera.Angry");
+ SetEyeState(CAMERA_EYE_ANGRY);
+ }
+ else if ((!bAngry) && (m_bAngry))
+ {
+ m_bAngry = false;
+
+ // make sure the flash is off (we might be in mid-flash)
+ m_pEyeFlash->SetBrightness(0);
+ SetEyeState(GetTarget() ? CAMERA_EYE_SEEKING_TARGET : CAMERA_EYE_IDLE);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::InputSetAngry(inputdata_t &inputdata)
+{
+ SetAngry(true);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::InputSetIdle(inputdata_t &inputdata)
+{
+ SetAngry(false);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::DeathThink()
+{
+ if (PreThink(CAMERA_DEAD))
+ return;
+
+ // Level out our angles
+ m_vecGoalAngles = GetAbsAngles();
+ SetNextThink( gpGlobals->curtime + 0.1f );
+
+ if (m_lifeState != LIFE_DEAD)
+ {
+ m_lifeState = LIFE_DEAD;
+
+ EmitSound("NPC_CombineCamera.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);
+
+ SetActivity((Activity) ACT_COMBINE_CAMERA_CLOSE);
+ }
+
+ StudioFrameAdvance();
+
+ if (IsActivityFinished() && (UpdateFacing() == false))
+ {
+ SetHeight(COMBINE_CAMERA_RETRACT_HEIGHT);
+
+ m_flPlaybackRate = 0;
+ SetThink(NULL);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : height -
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::SetHeight(float height)
+{
+ Vector forward, right, up;
+ AngleVectors(GetLocalAngles(), &forward, &right, &up);
+
+ Vector mins = (forward * -16.0f) + (right * -16.0f);
+ Vector maxs = (forward * 16.0f) + (right * 16.0f) + (up * -height);
+
+ if (mins.x > maxs.x)
+ {
+ V_swap(mins.x, maxs.x);
+ }
+
+ if (mins.y > maxs.y)
+ {
+ V_swap(mins.y, maxs.y);
+ }
+
+ if (mins.z > maxs.z)
+ {
+ V_swap(mins.z, maxs.z);
+ }
+
+ SetCollisionBounds(mins, maxs);
+
+ UTIL_SetSize(this, mins, maxs);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Draw any debug text overlays
+//-----------------------------------------------------------------------------
+int CNPC_CombineCamera::DrawDebugTextOverlays(void)
+{
+ int text_offset = BaseClass::DrawDebugTextOverlays();
+
+ if (m_debugOverlays & OVERLAY_TEXT_BIT)
+ {
+ char tempstr[512];
+
+ Q_snprintf( tempstr, sizeof( tempstr ),"Enemy : %s", m_hEnemyTarget ? m_hEnemyTarget->GetDebugName() : "<none>");
+ EntityText(text_offset,tempstr,0);
+ text_offset++;
+ }
+ return text_offset;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_CombineCamera::DrawDebugGeometryOverlays(void)
+{
+ // ------------------------------
+ // Draw viewcone if selected
+ // ------------------------------
+ if ((m_debugOverlays & OVERLAY_NPC_VIEWCONE_BIT))
+ {
+ float flViewRange = acos(CAMERA_FOV_NARROW);
+ Vector vEyeDir = EyeDirection2D( );
+ Vector vLeftDir, vRightDir;
+ float fSin, fCos;
+ SinCos( flViewRange, &fSin, &fCos );
+
+ vLeftDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
+ vLeftDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
+ vLeftDir.z = vEyeDir.z;
+ fSin = sin(-flViewRange);
+ fCos = cos(-flViewRange);
+ vRightDir.x = vEyeDir.x * fCos - vEyeDir.y * fSin;
+ vRightDir.y = vEyeDir.x * fSin + vEyeDir.y * fCos;
+ vRightDir.z = vEyeDir.z;
+
+ NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-40), Vector(200,0,40), vLeftDir, 255, 255, 0, 50, 0 );
+ NDebugOverlay::BoxDirection(EyePosition(), Vector(0,0,-40), Vector(200,0,40), vRightDir, 255, 255, 0, 50, 0 );
+ NDebugOverlay::Box(EyePosition(), -Vector(2,2,2), Vector(2,2,2), 255, 255, 0, 128, 0 );
+ }
+
+ BaseClass::DrawDebugGeometryOverlays();
+}