diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /game/server/hl2/npc_combinecamera.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'game/server/hl2/npc_combinecamera.cpp')
| -rw-r--r-- | game/server/hl2/npc_combinecamera.cpp | 1188 |
1 files changed, 1188 insertions, 0 deletions
diff --git a/game/server/hl2/npc_combinecamera.cpp b/game/server/hl2/npc_combinecamera.cpp new file mode 100644 index 0000000..26d1f07 --- /dev/null +++ b/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(); +} |