Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
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.
 
 
 
 
 
 

1188 lines
32 KiB

//========= 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();
}