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