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