summaryrefslogtreecommitdiff
path: root/game/server/hl2/npc_spotlight.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'game/server/hl2/npc_spotlight.cpp')
-rw-r--r--game/server/hl2/npc_spotlight.cpp1544
1 files changed, 1544 insertions, 0 deletions
diff --git a/game/server/hl2/npc_spotlight.cpp b/game/server/hl2/npc_spotlight.cpp
new file mode 100644
index 0000000..d36478d
--- /dev/null
+++ b/game/server/hl2/npc_spotlight.cpp
@@ -0,0 +1,1544 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#include "cbase.h"
+#include "ai_basenpc.h"
+#include "AI_Default.h"
+#include "AI_Senses.h"
+#include "ai_node.h" // for hint defintions
+#include "ai_network.h"
+#include "AI_Hint.h"
+#include "ai_squad.h"
+#include "beam_shared.h"
+#include "globalstate.h"
+#include "soundent.h"
+#include "ndebugoverlay.h"
+#include "entitylist.h"
+#include "npc_citizen17.h"
+#include "scriptedtarget.h"
+#include "ai_interactions.h"
+#include "spotlightend.h"
+#include "beam_flags.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define SPOTLIGHT_SWING_FORWARD 1
+#define SPOTLIGHT_SWING_BACK -1
+
+//------------------------------------
+// Spawnflags
+//------------------------------------
+#define SF_SPOTLIGHT_START_TRACK_ON (1 << 16)
+#define SF_SPOTLIGHT_START_LIGHT_ON (1 << 17)
+#define SF_SPOTLIGHT_NO_DYNAMIC_LIGHT (1 << 18)
+#define SF_SPOTLIGHT_NEVER_MOVE (1 << 19)
+
+
+//-----------------------------------------------------------------------------
+// Parameters for how the spotlight behaves
+//-----------------------------------------------------------------------------
+#define SPOTLIGHT_ENTITY_INSPECT_LENGTH 15 // How long does the inspection last
+#define SPOTLIGHT_HINT_INSPECT_LENGTH 15 // How long does the inspection last
+#define SPOTLIGHT_SOUND_INSPECT_LENGTH 1 // How long does the inspection last
+
+#define SPOTLIGHT_HINT_INSPECT_DELAY 20 // Check for hint nodes this often
+#define SPOTLIGHT_ENTITY_INSPECT_DELAY 1 // Check for citizens this often
+
+#define SPOTLIGHT_HINT_SEARCH_DIST 1000 // How far from self do I look for hints?
+#define SPOTLIGHT_ENTITY_SEARCH_DIST 100 // How far from spotlight do I look for entities?
+#define SPOTLIGHT_ACTIVE_SEARCH_DIST 200 // How far from spotlight do I look when already have entity
+
+#define SPOTLIGHT_BURN_TARGET_THRESH 60 // How close need to get to burn target
+#define SPOTLIGHT_MAX_SPEED_SCALE 2
+
+//#define SPOTLIGHT_DEBUG
+
+
+// -----------------------------------
+// Spotlight flags
+// -----------------------------------
+enum SpotlightFlags_t
+{
+ BITS_SPOTLIGHT_LIGHT_ON = 0x00000001, // Light is on
+ BITS_SPOTLIGHT_TRACK_ON = 0x00000002, // Tracking targets / scanning
+ BITS_SPOTLIGHT_SMOOTH_RETURN = 0x00001000, // If out of range, don't pop back to position
+};
+
+
+class CBeam;
+
+
+class CNPC_Spotlight : public CAI_BaseNPC
+{
+ DECLARE_CLASS( CNPC_Spotlight, CAI_BaseNPC );
+
+ public:
+ CNPC_Spotlight();
+ Class_T Classify(void);
+ int UpdateTransmitState(void);
+ void Event_Killed( const CTakeDamageInfo &info );
+ int OnTakeDamage_Alive( const CTakeDamageInfo &info );
+ int GetSoundInterests( void );
+
+ bool FValidateHintType(CAI_Hint *pHint);
+
+ Disposition_t IRelationType(CBaseEntity *pTarget);
+ float HearingSensitivity( void ) { return 4.0; };
+
+ void NPCThink(void);
+ bool HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt);
+
+ void UpdateTargets(void);
+ void Precache(void);
+ void Spawn(void);
+
+ public:
+
+ int m_fSpotlightFlags;
+
+ // ------------------------------
+ // Scripted Spotlight Motion
+ // ------------------------------
+ CScriptedTarget* m_pScriptedTarget; // My current scripted target
+ void SetScriptedTarget( CScriptedTarget *pScriptedTarget );
+
+ // ------------------------------
+ // Inspecting
+ // ------------------------------
+ Vector m_vInspectPos;
+ float m_flInspectEndTime;
+ float m_flNextEntitySearchTime;
+ float m_flNextHintSearchTime; // Time to look for hints to inspect
+
+ void SetInspectTargetToEntity(CBaseEntity *pEntity, float fInspectDuration);
+ void SetInspectTargetToEnemy(CBaseEntity *pEntity);
+ void SetInspectTargetToPos(const Vector &vInspectPos, float fInspectDuration);
+ void SetInspectTargetToHint(CAI_Hint *pHint, float fInspectDuration);
+ void ClearInspectTarget(void);
+ bool HaveInspectTarget(void);
+ Vector InspectTargetPosition(void);
+ CBaseEntity* BestInspectTarget(void);
+ void RequestInspectSupport(void);
+
+ // -------------------------------
+ // Outputs
+ // -------------------------------
+ bool m_bHadEnemy; // Had an enemy
+ COutputEvent m_pOutputAlert; // Alerted by sound
+ COutputEHANDLE m_pOutputDetect; // Found enemy, output it's name
+ COutputEHANDLE m_pOutputLost; // Lost enemy
+ COutputEHANDLE m_pOutputSquadDetect; // Squad Found enemy
+ COutputEHANDLE m_pOutputSquadLost; // Squad Lost enemy
+ COutputVector m_pOutputPosition; // End position of spotlight beam
+
+ // ------------------------------
+ // Spotlight
+ // ------------------------------
+ float m_flYaw;
+ float m_flYawCenter;
+ float m_flYawRange; // +/- around center
+ float m_flYawSpeed;
+ float m_flYawDir;
+
+ float m_flPitch;
+ float m_flPitchCenter;
+ float m_flPitchMin;
+ float m_flPitchMax;
+ float m_flPitchSpeed;
+ float m_flPitchDir;
+
+ float m_flIdleSpeed; // Speed when no enemy
+ float m_flAlertSpeed; // Speed when found enemy
+
+ Vector m_vSpotlightTargetPos;
+ Vector m_vSpotlightCurrentPos;
+ CBeam* m_pSpotlight;
+ CSpotlightEnd* m_pSpotlightTarget;
+ Vector m_vSpotlightDir;
+ int m_nHaloSprite;
+
+ float m_flSpotlightMaxLength;
+ float m_flSpotlightCurLength;
+ float m_flSpotlightGoalWidth;
+
+ void SpotlightUpdate(void);
+ Vector SpotlightCurrentPos(void);
+ void SpotlightSetTargetYawAndPitch(void);
+ float SpotlightSpeed(void);
+ void SpotlightCreate(void);
+ void SpotlightDestroy(void);
+ bool SpotlightIsPositionLegal(const Vector &vTestPos);
+
+ // ------------------------------
+ // Inputs
+ // ------------------------------
+ void InputLightOn( inputdata_t &inputdata );
+ void InputLightOff( inputdata_t &inputdata );
+ void InputTrackOn( inputdata_t &inputdata );
+ void InputTrackOff( inputdata_t &inputdata );
+
+ protected:
+
+ DECLARE_DATADESC();
+};
+
+
+BEGIN_DATADESC( CNPC_Spotlight )
+ DEFINE_FIELD( m_vInspectPos, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_flYaw, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flYawCenter, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flYawSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flYawDir, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flPitch, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flPitchCenter, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flPitchSpeed, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flPitchDir, FIELD_FLOAT ),
+ DEFINE_FIELD( m_flSpotlightCurLength, FIELD_FLOAT ),
+
+ DEFINE_FIELD( m_fSpotlightFlags, FIELD_INTEGER ),
+ DEFINE_FIELD( m_flInspectEndTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextEntitySearchTime, FIELD_TIME ),
+ DEFINE_FIELD( m_flNextHintSearchTime, FIELD_TIME ),
+ DEFINE_FIELD( m_bHadEnemy, FIELD_BOOLEAN ),
+ DEFINE_FIELD( m_vSpotlightTargetPos, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_vSpotlightCurrentPos, FIELD_POSITION_VECTOR ),
+ DEFINE_FIELD( m_pSpotlight, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_pSpotlightTarget, FIELD_CLASSPTR ),
+ DEFINE_FIELD( m_vSpotlightDir, FIELD_VECTOR ),
+ DEFINE_FIELD( m_nHaloSprite, FIELD_INTEGER ),
+ DEFINE_FIELD( m_pScriptedTarget, FIELD_CLASSPTR ),
+
+ DEFINE_KEYFIELD( m_flYawRange, FIELD_FLOAT, "YawRange"),
+ DEFINE_KEYFIELD( m_flPitchMin, FIELD_FLOAT, "PitchMin"),
+ DEFINE_KEYFIELD( m_flPitchMax, FIELD_FLOAT, "PitchMax"),
+ DEFINE_KEYFIELD( m_flIdleSpeed, FIELD_FLOAT, "IdleSpeed"),
+ DEFINE_KEYFIELD( m_flAlertSpeed, FIELD_FLOAT, "AlertSpeed"),
+ DEFINE_KEYFIELD( m_flSpotlightMaxLength,FIELD_FLOAT, "SpotlightLength"),
+ DEFINE_KEYFIELD( m_flSpotlightGoalWidth,FIELD_FLOAT, "SpotlightWidth"),
+
+ // DEBUG m_pScriptedTarget
+
+ // Inputs
+ DEFINE_INPUTFUNC( FIELD_VOID, "LightOn", InputLightOn ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "LightOff", InputLightOff ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "TrackOn", InputTrackOn ),
+ DEFINE_INPUTFUNC( FIELD_VOID, "TrackOff", InputTrackOff ),
+
+ // Outputs
+ DEFINE_OUTPUT(m_pOutputAlert, "OnAlert" ),
+ DEFINE_OUTPUT(m_pOutputDetect, "DetectedEnemy" ),
+ DEFINE_OUTPUT(m_pOutputLost, "LostEnemy" ),
+ DEFINE_OUTPUT(m_pOutputSquadDetect, "SquadDetectedEnemy" ),
+ DEFINE_OUTPUT(m_pOutputSquadLost, "SquadLostEnemy" ),
+ DEFINE_OUTPUT(m_pOutputPosition, "LightPosition" ),
+
+END_DATADESC()
+
+
+LINK_ENTITY_TO_CLASS(npc_spotlight, CNPC_Spotlight);
+
+CNPC_Spotlight::CNPC_Spotlight()
+{
+#ifdef _DEBUG
+ m_vInspectPos.Init();
+ m_vSpotlightTargetPos.Init();
+ m_vSpotlightCurrentPos.Init();
+ m_vSpotlightDir.Init();
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Indicates this NPC's place in the relationship table.
+//-----------------------------------------------------------------------------
+Class_T CNPC_Spotlight::Classify(void)
+{
+ return(CLASS_MILITARY);
+}
+
+//-------------------------------------------------------------------------------------
+// Purpose : Send even though we don't have a model so spotlight gets proper position
+// Input :
+// Output :
+//-------------------------------------------------------------------------------------
+int CNPC_Spotlight::UpdateTransmitState(void)
+{
+ return SetTransmitState( FL_EDICT_PVSCHECK );
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+int CNPC_Spotlight::GetSoundInterests( void )
+{
+ return (SOUND_COMBAT | SOUND_DANGER);
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Override to split in two when attacked
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+int CNPC_Spotlight::OnTakeDamage_Alive( const CTakeDamageInfo &info )
+{
+ // Deflect spotlight
+ Vector vCrossProduct;
+ CrossProduct(m_vSpotlightDir,g_vecAttackDir, vCrossProduct);
+ if (vCrossProduct.y > 0)
+ {
+ m_flYaw += random->RandomInt(10,20);
+ }
+ else
+ {
+ m_flYaw -= random->RandomInt(10,20);
+ }
+
+ return (BaseClass::OnTakeDamage_Alive( info ));
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pInflictor -
+// pAttacker -
+// flDamage -
+// bitsDamageType -
+//-----------------------------------------------------------------------------
+void CNPC_Spotlight::Event_Killed( const CTakeDamageInfo &info )
+{
+ // Interrupt whatever schedule I'm on
+ SetCondition(COND_SCHEDULE_DONE);
+
+ // Remove spotlight
+ SpotlightDestroy();
+
+ // Otherwise, turn into a physics object and fall to the ground
+ CBaseCombatCharacter::Event_Killed( info );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Tells use whether or not the NPC cares about a given type of hint node.
+// Input : sHint -
+// Output : TRUE if the NPC is interested in this hint type, FALSE if not.
+//-----------------------------------------------------------------------------
+bool CNPC_Spotlight::FValidateHintType(CAI_Hint *pHint)
+{
+ if (pHint->HintType() == HINT_WORLD_WINDOW)
+ {
+ Vector vHintPos;
+ pHint->GetPosition(this,&vHintPos);
+ if (SpotlightIsPositionLegal(vHintPos))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Plays the engine sound.
+//-----------------------------------------------------------------------------
+void CNPC_Spotlight::NPCThink(void)
+{
+ SetNextThink( gpGlobals->curtime + 0.1f );// keep npc thinking.
+
+ if (CAI_BaseNPC::m_nDebugBits & bits_debugDisableAI)
+ {
+ if (m_pSpotlightTarget)
+ {
+ m_pSpotlightTarget->SetAbsVelocity( vec3_origin );
+ }
+ }
+ else if (IsAlive())
+ {
+ GetSenses()->Listen();
+ UpdateTargets();
+ SpotlightUpdate();
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Spotlight::Precache(void)
+{
+ //
+ // Model.
+ //
+ PrecacheModel("models/combot.mdl");
+ PrecacheModel("models/gibs/combot_gibs.mdl");
+
+ //
+ // Sprites.
+ //
+ PrecacheModel("sprites/spotlight.vmt");
+ m_nHaloSprite = PrecacheModel("sprites/blueflare1.vmt");
+
+ BaseClass::Precache();
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+CBaseEntity* CNPC_Spotlight::BestInspectTarget(void)
+{
+ // Only look for inspect targets when spotlight it on
+ if (m_pSpotlightTarget == NULL)
+ {
+ return NULL;
+ }
+
+ float fBestDistance = MAX_COORD_RANGE;
+ int nBestPriority = -1000;
+ int nBestRelationship = D_NU;
+
+ // Get my best enemy first
+ CBaseEntity* pBestEntity = BestEnemy();
+ if (pBestEntity)
+ {
+ // If the enemy isn't visibile
+ if (!FVisible(pBestEntity))
+ {
+ // If he hasn't been seen in a while and hasn't already eluded
+ // the squad, make the enemy as eluded and fire a lost squad output
+ float flTimeLastSeen = GetEnemies()->LastTimeSeen(pBestEntity);
+ if (!GetEnemies()->HasEludedMe(pBestEntity) &&
+ flTimeLastSeen + 0.5 < gpGlobals->curtime)
+ {
+ GetEnemies()->MarkAsEluded(pBestEntity);
+ m_pOutputSquadLost.Set(*((EHANDLE *)pBestEntity),this,this);
+ }
+ pBestEntity = NULL;
+ }
+
+ // If he has eluded me or isn't in the legal range of my spotligth reject
+ else if (GetEnemies()->HasEludedMe(pBestEntity) ||
+ !SpotlightIsPositionLegal(GetEnemies()->LastKnownPosition(pBestEntity)) )
+ {
+ pBestEntity = NULL;
+ }
+ }
+
+ CBaseEntity *pEntity = NULL;
+
+ // Search from the spotlight position
+ Vector vSearchOrigin = m_pSpotlightTarget->GetAbsOrigin();
+ float flSearchDist;
+ if (HaveInspectTarget())
+ {
+ flSearchDist = SPOTLIGHT_ACTIVE_SEARCH_DIST;
+ }
+ else
+ {
+ flSearchDist = SPOTLIGHT_ENTITY_SEARCH_DIST;
+ }
+ for ( CEntitySphereQuery sphere( vSearchOrigin, SPOTLIGHT_ENTITY_SEARCH_DIST ); pEntity = sphere.GetCurrentEntity(); sphere.NextEntity() )
+ {
+ if (pEntity->GetFlags() & (FL_CLIENT|FL_NPC))
+ {
+
+ if (pEntity->GetFlags() & FL_NOTARGET)
+ {
+ continue;
+ }
+
+
+ if (!pEntity->IsAlive())
+ {
+ continue;
+ }
+
+ if ((pEntity->Classify() == CLASS_MILITARY)||
+ (pEntity->Classify() == CLASS_BULLSEYE))
+ {
+ continue;
+ }
+
+ if (m_pSquad && m_pSquad->SquadIsMember(pEntity))
+ {
+ continue;
+ }
+
+ // Disregard if the entity is out of the view cone, occluded,
+ if( !FVisible( pEntity ) )
+ {
+ continue;
+ }
+
+ // If it's a new enemy or one that had eluded me
+ if (!GetEnemies()->HasMemory(pEntity) ||
+ GetEnemies()->HasEludedMe(pEntity) )
+ {
+ m_pOutputSquadDetect.Set(*((EHANDLE *)pEntity),this,this);
+ }
+ UpdateEnemyMemory(pEntity,pEntity->GetAbsOrigin());
+
+ CBaseCombatCharacter* pBCC = (CBaseCombatCharacter*)pEntity;
+ float fTestDistance = (GetAbsOrigin() - pBCC->EyePosition()).Length();
+ int nTestRelationship = IRelationType(pBCC);
+ int nTestPriority = IRelationPriority ( pBCC );
+
+ // Only follow hated entities if I'm not in idle state
+ if (nTestRelationship != D_HT && m_NPCState != NPC_STATE_IDLE)
+ {
+ continue;
+ }
+
+ // -------------------------------------------
+ // Choose hated entites over everything else
+ // -------------------------------------------
+ if (nTestRelationship == D_HT && nBestRelationship != D_HT)
+ {
+ pBestEntity = pBCC;
+ fBestDistance = fTestDistance;
+ nBestPriority = nTestPriority;
+ nBestRelationship = nTestRelationship;
+ }
+ // -------------------------------------------
+ // If both are hated, or both are not
+ // -------------------------------------------
+ else if( (nTestRelationship != D_HT && nBestRelationship != D_HT) ||
+ (nTestRelationship == D_HT && nBestRelationship == D_HT) )
+ {
+ // --------------------------------------
+ // Pick one with the higher priority
+ // --------------------------------------
+ if (nTestPriority > nBestPriority)
+ {
+ pBestEntity = pBCC;
+ fBestDistance = fTestDistance;
+ nBestPriority = nTestPriority;
+ nBestRelationship = nTestRelationship;
+ }
+ // -----------------------------------------
+ // If priority the same pick best distance
+ // -----------------------------------------
+ else if (nTestPriority == nBestPriority)
+ {
+ if (fTestDistance < fBestDistance)
+ {
+ pBestEntity = pBCC;
+ fBestDistance = fTestDistance;
+ nBestPriority = nTestPriority;
+ nBestRelationship = nTestRelationship;
+ }
+ }
+ }
+ }
+ }
+ return pBestEntity;
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Clears any previous inspect target and set inspect target to
+// the given entity and set the durection of the inspection
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_Spotlight::SetInspectTargetToEntity(CBaseEntity *pEntity, float fInspectDuration)
+{
+ ClearInspectTarget();
+ SetTarget(pEntity);
+ m_flInspectEndTime = gpGlobals->curtime + fInspectDuration;
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Clears any previous inspect target and set inspect target to
+// the given entity and set the durection of the inspection
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_Spotlight::SetInspectTargetToEnemy(CBaseEntity *pEntity)
+{
+ ClearInspectTarget();
+ SetEnemy( pEntity );
+ m_bHadEnemy = true;
+ m_flInspectEndTime = 0;
+ SetState(NPC_STATE_COMBAT);
+
+ EHANDLE hEnemy;
+ hEnemy.Set( GetEnemy() );
+ m_pOutputDetect.Set(hEnemy, NULL, this);
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Clears any previous inspect target and set inspect target to
+// the given hint node and set the durection of the inspection
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_Spotlight::SetInspectTargetToHint(CAI_Hint *pHintNode, float fInspectDuration)
+{
+ ClearInspectTarget();
+
+ // --------------------------------------------
+ // Figure out the location that the hint hits
+ // --------------------------------------------
+ float fHintYaw = DEG2RAD(pHintNode->Yaw());
+ Vector vHintDir = Vector(cos(fHintYaw),sin(fHintYaw),0);
+ Vector vHintOrigin;
+ pHintNode->GetPosition(this,&vHintOrigin);
+ Vector vHintEnd = vHintOrigin + (vHintDir * 500);
+ trace_t tr;
+ AI_TraceLine ( vHintOrigin, vHintEnd, MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr);
+ if (tr.fraction == 1.0)
+ {
+ DevMsg("ERROR: Scanner hint node not facing a surface!\n");
+ }
+ else
+ {
+ SetHintNode( pHintNode );
+ m_vInspectPos = tr.endpos;
+ pHintNode->Lock(this);
+
+ m_flInspectEndTime = gpGlobals->curtime + fInspectDuration;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Clears any previous inspect target and set inspect target to
+// the given position and set the durection of the inspection
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_Spotlight::SetInspectTargetToPos(const Vector &vInspectPos, float fInspectDuration)
+{
+ ClearInspectTarget();
+ m_vInspectPos = vInspectPos;
+
+ m_flInspectEndTime = gpGlobals->curtime + fInspectDuration;
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Clears out any previous inspection targets
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_Spotlight::ClearInspectTarget(void)
+{
+ // If I'm losing an enemy, fire a message
+ if (m_bHadEnemy)
+ {
+ m_bHadEnemy = false;
+
+ EHANDLE hEnemy;
+ hEnemy.Set( GetEnemy() );
+
+ m_pOutputLost.Set(hEnemy,this,this);
+ }
+
+ // If I'm in combat state, go to alert
+ if (m_NPCState == NPC_STATE_COMBAT)
+ {
+ SetState(NPC_STATE_ALERT);
+ }
+
+ SetTarget( NULL );
+ SetEnemy( NULL );
+ ClearHintNode( SPOTLIGHT_HINT_INSPECT_LENGTH );
+ m_vInspectPos = vec3_origin;
+ m_flYawDir = random->RandomInt(0,1) ? 1 : -1;
+ m_flPitchDir = random->RandomInt(0,1) ? 1 : -1;
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Returns true if there is a position to be inspected and sets
+// vTargetPos to the inspection position
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+bool CNPC_Spotlight::HaveInspectTarget(void)
+{
+ if (GetEnemy() != NULL)
+ {
+ return true;
+ }
+ else if (GetTarget() != NULL)
+ {
+ return true;
+ }
+ if (m_vInspectPos != vec3_origin)
+ {
+ return true;
+ }
+ return false;
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Returns true if there is a position to be inspected and sets
+// vTargetPos to the inspection position
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+Vector CNPC_Spotlight::InspectTargetPosition(void)
+{
+ if (GetEnemy() != NULL)
+ {
+ // If in spotlight mode, aim for ground below target unless is client
+ if (!(GetEnemy()->GetFlags() & FL_CLIENT))
+ {
+ Vector vInspectPos;
+ vInspectPos.x = GetEnemy()->GetAbsOrigin().x;
+ vInspectPos.y = GetEnemy()->GetAbsOrigin().y;
+ vInspectPos.z = GetFloorZ(GetEnemy()->GetAbsOrigin()+Vector(0,0,1));
+ return vInspectPos;
+ }
+ // Otherwise aim for eyes
+ else
+ {
+ return GetEnemy()->EyePosition();
+ }
+ }
+ else if (GetTarget() != NULL)
+ {
+ // If in spotlight mode, aim for ground below target unless is client
+ if (!(GetTarget()->GetFlags() & FL_CLIENT))
+ {
+ Vector vInspectPos;
+ vInspectPos.x = GetTarget()->GetAbsOrigin().x;
+ vInspectPos.y = GetTarget()->GetAbsOrigin().y;
+ vInspectPos.z = GetFloorZ(GetTarget()->GetAbsOrigin());
+ return vInspectPos;
+ }
+ // Otherwise aim for eyes
+ else
+ {
+ return GetTarget()->EyePosition();
+ }
+ }
+ else if (m_vInspectPos != vec3_origin)
+ {
+ return m_vInspectPos;
+ }
+ else
+ {
+ DevMsg("InspectTargetPosition called with no target!\n");
+ return m_vInspectPos;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_Spotlight::UpdateTargets(void)
+{
+ if (m_fSpotlightFlags & BITS_SPOTLIGHT_TRACK_ON)
+ {
+ // --------------------------------------------------------------------------
+ // Look for a nearby entity to inspect
+ // --------------------------------------------------------------------------
+ CBaseEntity *pBestEntity = BestInspectTarget();
+
+ // If I found one
+ if (pBestEntity)
+ {
+ // If it's an enemy
+ if (IRelationType(pBestEntity) == D_HT)
+ {
+ // If I'm not already inspecting an enemy take it
+ if (GetEnemy() == NULL)
+ {
+ SetInspectTargetToEnemy(pBestEntity);
+ if (m_pSquad)
+ {
+ AISquadIter_t iter;
+ for (CAI_BaseNPC *pSquadMember = m_pSquad->GetFirstMember( &iter ); pSquadMember; pSquadMember = m_pSquad->GetNextMember( &iter ) )
+ {
+ // reset members who aren't activly engaged in fighting
+ if (pSquadMember->GetEnemy() != pBestEntity && !pSquadMember->HasCondition( COND_SEE_ENEMY))
+ {
+ // give them a new enemy
+ pSquadMember->SetLastAttackTime( 0 );
+ pSquadMember->SetCondition ( COND_NEW_ENEMY );
+ }
+ }
+ }
+ }
+ // If I am inspecting an enemy, take it if priority is higher
+ else
+ {
+ if (IRelationPriority(pBestEntity) > IRelationPriority(GetEnemy()))
+ {
+ SetInspectTargetToEnemy(pBestEntity);
+ }
+ }
+ }
+ // If its not an enemy
+ else
+ {
+ // If I'm not already inspeting something take it
+ if (GetTarget() == NULL)
+ {
+ SetInspectTargetToEntity(pBestEntity,SPOTLIGHT_ENTITY_INSPECT_LENGTH);
+ }
+ // If I am inspecting somethin, take if priority is higher
+ else
+ {
+ if (IRelationPriority(pBestEntity) > IRelationPriority(GetTarget()))
+ {
+ SetInspectTargetToEntity(pBestEntity,SPOTLIGHT_ENTITY_INSPECT_LENGTH);
+ }
+ }
+ }
+ }
+
+ // ---------------------------------------
+ // If I'm not current inspecting an enemy
+ // ---------------------------------------
+ if (GetEnemy() == NULL)
+ {
+ // -----------------------------------------------------------
+ // If my inspection over clear my inspect target.
+ // -----------------------------------------------------------
+ if (HaveInspectTarget() &&
+ gpGlobals->curtime > m_flInspectEndTime )
+ {
+ m_flNextEntitySearchTime = gpGlobals->curtime + SPOTLIGHT_ENTITY_INSPECT_DELAY;
+ m_flNextHintSearchTime = gpGlobals->curtime + SPOTLIGHT_HINT_INSPECT_DELAY;
+ ClearInspectTarget();
+ }
+
+ // --------------------------------------------------------------
+ // If I heard a sound inspect it
+ // --------------------------------------------------------------
+ if (HasCondition(COND_HEAR_COMBAT) || HasCondition(COND_HEAR_DANGER) )
+ {
+ CSound *pSound = GetBestSound();
+ if (pSound)
+ {
+ Vector vSoundPos = pSound->GetSoundOrigin();
+ // Only alert to sound if in my swing range
+ if (SpotlightIsPositionLegal(vSoundPos))
+ {
+ SetInspectTargetToPos(vSoundPos,SPOTLIGHT_SOUND_INSPECT_LENGTH);
+
+ // Fire alert output
+ m_pOutputAlert.FireOutput(NULL,this);
+
+ SetState(NPC_STATE_ALERT);
+ }
+ }
+ }
+
+ // --------------------------------------
+ // Check for hints to inspect
+ // --------------------------------------
+ if (gpGlobals->curtime > m_flNextHintSearchTime &&
+ !HaveInspectTarget() )
+ {
+ SetHintNode(CAI_HintManager::FindHint(this, HINT_NONE, 0, SPOTLIGHT_HINT_SEARCH_DIST));
+
+ if (GetHintNode())
+ {
+ m_flNextHintSearchTime = gpGlobals->curtime + SPOTLIGHT_HINT_INSPECT_LENGTH;
+ SetInspectTargetToHint(GetHintNode(),SPOTLIGHT_HINT_INSPECT_LENGTH);
+ }
+ }
+ }
+
+ // -------------------------------------------------------
+ // Make sure inspect target is still in a legal position
+ // (Don't care about enemies)
+ // -------------------------------------------------------
+ if (GetTarget())
+ {
+ if (!SpotlightIsPositionLegal(GetEnemies()->LastKnownPosition(GetTarget())))
+ {
+ ClearInspectTarget();
+ }
+ else if (!FVisible(GetTarget()))
+ {
+ ClearInspectTarget();
+ }
+ }
+ if (GetEnemy())
+ {
+ if (!FVisible(GetEnemy()))
+ {
+ ClearInspectTarget();
+ }
+ // If enemy is dead inspect for a couple of seconds on move on
+ else if (!GetEnemy()->IsAlive())
+ {
+ SetInspectTargetToPos( GetEnemy()->GetAbsOrigin(), 1.0);
+ }
+ else
+ {
+ UpdateEnemyMemory(GetEnemy(),GetEnemy()->GetAbsOrigin());
+ }
+ }
+
+ // -----------------------------------------
+ // See if I'm at my burn target
+ // ------------------------------------------
+ if (!HaveInspectTarget() &&
+ m_pScriptedTarget &&
+ m_pSpotlightTarget != NULL )
+ {
+ float fTargetDist = (m_vSpotlightTargetPos - m_vSpotlightCurrentPos).Length();
+ if (fTargetDist < SPOTLIGHT_BURN_TARGET_THRESH )
+ {
+ // Update scripted target
+ SetScriptedTarget( m_pScriptedTarget->NextScriptedTarget());
+ }
+ else
+ {
+ Vector vTargetDir = m_vSpotlightTargetPos - m_vSpotlightCurrentPos;
+ VectorNormalize(vTargetDir);
+ float flDot = DotProduct(m_vSpotlightDir,vTargetDir);
+ if (flDot > 0.99 )
+ {
+ // Update scripted target
+ SetScriptedTarget( m_pScriptedTarget->NextScriptedTarget());
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Overridden because if the player is a criminal, we hate them.
+// Input : pTarget - Entity with which to determine relationship.
+// Output : Returns relationship value.
+//-----------------------------------------------------------------------------
+Disposition_t CNPC_Spotlight::IRelationType(CBaseEntity *pTarget)
+{
+ //
+ // If it's the player and they are a criminal, we hate them.
+ //
+ if (pTarget->Classify() == CLASS_PLAYER)
+ {
+ if (GlobalEntity_GetState("gordon_precriminal") == GLOBAL_ON)
+ {
+ return(D_NU);
+ }
+ }
+
+ return(CBaseCombatCharacter::IRelationType(pTarget));
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_Spotlight::SpotlightDestroy(void)
+{
+ if (m_pSpotlight)
+ {
+ UTIL_Remove(m_pSpotlight);
+ m_pSpotlight = NULL;
+
+ UTIL_Remove(m_pSpotlightTarget);
+ m_pSpotlightTarget = NULL;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_Spotlight::SpotlightCreate(void)
+{
+
+ // If I have an enemy, start spotlight on my enemy
+ if (GetEnemy() != NULL)
+ {
+ Vector vEnemyPos = GetEnemyLKP();
+ Vector vTargetPos = vEnemyPos;
+ vTargetPos.z = GetFloorZ(vEnemyPos);
+ m_vSpotlightDir = vTargetPos - GetAbsOrigin();
+ VectorNormalize(m_vSpotlightDir);
+ }
+ // If I have an target, start spotlight on my target
+ else if (GetTarget() != NULL)
+ {
+ Vector vTargetPos = GetTarget()->GetAbsOrigin();
+ vTargetPos.z = GetFloorZ(GetTarget()->GetAbsOrigin());
+ m_vSpotlightDir = vTargetPos - GetAbsOrigin();
+ VectorNormalize(m_vSpotlightDir);
+ }
+ else
+ {
+ AngleVectors( GetAbsAngles(), &m_vSpotlightDir );
+ }
+
+ trace_t tr;
+ AI_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + m_vSpotlightDir * m_flSpotlightMaxLength,
+ MASK_OPAQUE, this, COLLISION_GROUP_NONE, &tr);
+
+ m_pSpotlightTarget = (CSpotlightEnd*)CreateEntityByName( "spotlight_end" );
+ m_pSpotlightTarget->Spawn();
+ m_pSpotlightTarget->SetLocalOrigin( tr.endpos );
+ m_pSpotlightTarget->SetOwnerEntity( this );
+ m_pSpotlightTarget->m_clrRender = m_clrRender;
+ m_pSpotlightTarget->m_Radius = m_flSpotlightMaxLength;
+
+ if ( FBitSet (m_spawnflags, SF_SPOTLIGHT_NO_DYNAMIC_LIGHT) )
+ {
+ m_pSpotlightTarget->m_flLightScale = 0.0;
+ }
+
+ m_pSpotlight = CBeam::BeamCreate( "sprites/spotlight.vmt", 2.0 );
+ m_pSpotlight->SetColor( m_clrRender->r, m_clrRender->g, m_clrRender->b );
+ m_pSpotlight->SetHaloTexture(m_nHaloSprite);
+ m_pSpotlight->SetHaloScale(40);
+ m_pSpotlight->SetEndWidth(m_flSpotlightGoalWidth);
+ m_pSpotlight->SetBeamFlags(FBEAM_SHADEOUT);
+ m_pSpotlight->SetBrightness( 80 );
+ m_pSpotlight->SetNoise( 0 );
+ m_pSpotlight->EntsInit( this, m_pSpotlightTarget );
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Returns true is spotlight can reach position
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+bool CNPC_Spotlight::SpotlightIsPositionLegal(const Vector &vTestPos)
+{
+ Vector vTargetDir = vTestPos - GetAbsOrigin();
+ VectorNormalize(vTargetDir);
+ QAngle vTargetAngles;
+ VectorAngles(vTargetDir,vTargetAngles);
+
+ // Make sure target is in a legal position
+ if (UTIL_AngleDistance( vTargetAngles[YAW], m_flYawCenter ) > m_flYawRange)
+ {
+ return false;
+ }
+ else if (UTIL_AngleDistance( vTargetAngles[YAW], m_flYawCenter ) < -m_flYawRange)
+ {
+ return false;
+ }
+ if (UTIL_AngleDistance( vTargetAngles[PITCH], m_flPitchCenter ) > m_flPitchMax)
+ {
+ return false;
+ }
+ else if (UTIL_AngleDistance( vTargetAngles[PITCH], m_flPitchCenter ) < m_flPitchMin)
+ {
+ return false;
+ }
+ return true;
+}
+
+//------------------------------------------------------------------------------
+// Purpose : Converts spotlight target position into desired yaw and pitch
+// directions to reach target
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_Spotlight::SpotlightSetTargetYawAndPitch(void)
+{
+ Vector vTargetDir = m_vSpotlightTargetPos - GetAbsOrigin();
+ VectorNormalize(vTargetDir);
+ QAngle vTargetAngles;
+ VectorAngles(vTargetDir,vTargetAngles);
+
+ float flYawDiff = UTIL_AngleDistance(vTargetAngles[YAW], m_flYaw);
+ if ( flYawDiff > 0)
+ {
+ m_flYawDir = SPOTLIGHT_SWING_FORWARD;
+ }
+ else
+ {
+ m_flYawDir = SPOTLIGHT_SWING_BACK;
+ }
+
+ //DevMsg("%f %f (%f)\n",vTargetAngles[YAW], m_flYaw,flYawDiff);
+
+ float flPitchDiff = UTIL_AngleDistance(vTargetAngles[PITCH], m_flPitch);
+ if (flPitchDiff > 0)
+ {
+ m_flPitchDir = SPOTLIGHT_SWING_FORWARD;
+ }
+ else
+ {
+ m_flPitchDir = SPOTLIGHT_SWING_BACK;
+ }
+
+ //DevMsg("%f %f (%f)\n",vTargetAngles[PITCH], m_flPitch,flPitchDiff);
+
+
+ if ( fabs(flYawDiff) < 2)
+ {
+ m_flYawDir *= 0.5;
+ }
+ if ( fabs(flPitchDiff) < 2)
+ {
+ m_flPitchDir *= 0.5;
+ }
+}
+
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+float CNPC_Spotlight::SpotlightSpeed(void)
+{
+ float fSpeedScale = 1.0;
+ float fInspectDist = (m_vSpotlightTargetPos - m_vSpotlightCurrentPos).Length();
+ if (fInspectDist < 100)
+ {
+ fSpeedScale = 0.25;
+ }
+
+ if (!HaveInspectTarget() && m_pScriptedTarget)
+ {
+ return (fSpeedScale * m_pScriptedTarget->MoveSpeed());
+ }
+ else if (m_NPCState == NPC_STATE_COMBAT ||
+ m_NPCState == NPC_STATE_ALERT )
+ {
+ return (fSpeedScale * m_flAlertSpeed);
+ }
+ else
+ {
+ return (fSpeedScale * m_flIdleSpeed);
+ }
+}
+//------------------------------------------------------------------------------
+// Purpose :
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+Vector CNPC_Spotlight::SpotlightCurrentPos(void)
+{
+ if (!m_pSpotlight)
+ {
+ DevMsg("Spotlight pos. called w/o spotlight!\n");
+ return vec3_origin;
+ }
+
+ if (HaveInspectTarget())
+ {
+ m_vSpotlightTargetPos = InspectTargetPosition();
+ SpotlightSetTargetYawAndPitch();
+ }
+ else if (m_pScriptedTarget)
+ {
+ m_vSpotlightTargetPos = m_pScriptedTarget->GetAbsOrigin();
+ SpotlightSetTargetYawAndPitch();
+
+ // I'm allowed to move outside my normal range when
+ // tracking burn targets. Return smoothly when I'm done
+ m_fSpotlightFlags |= BITS_SPOTLIGHT_SMOOTH_RETURN;
+ }
+ else
+ {
+ // Make random movement frame independent
+ if (random->RandomInt(0,10) == 0)
+ {
+ m_flYawDir *= -1;
+ }
+ if (random->RandomInt(0,10) == 0)
+ {
+ m_flPitchDir *= -1;
+ }
+ }
+ // Calculate new pitch and yaw velocity
+ float flSpeed = SpotlightSpeed();
+ float flNewYawSpeed = m_flYawDir * flSpeed;
+ float flNewPitchSpeed = m_flPitchDir * flSpeed;
+
+ // Adjust current velocity
+ float myYawDecay = 0.8;
+ float myPitchDecay = 0.7;
+ m_flYawSpeed = (myYawDecay * m_flYawSpeed + (1-myYawDecay) * flNewYawSpeed );
+ m_flPitchSpeed = (myPitchDecay * m_flPitchSpeed + (1-myPitchDecay) * flNewPitchSpeed);
+
+ // Keep speed with in bounds
+ float flMaxSpeed = SPOTLIGHT_MAX_SPEED_SCALE * SpotlightSpeed();
+ if (m_flYawSpeed > flMaxSpeed) m_flYawSpeed = flMaxSpeed;
+ else if (m_flYawSpeed < -flMaxSpeed) m_flYawSpeed = -flMaxSpeed;
+ if (m_flPitchSpeed > flMaxSpeed) m_flPitchSpeed = flMaxSpeed;
+ else if (m_flPitchSpeed < -flMaxSpeed) m_flPitchSpeed = -flMaxSpeed;
+
+ // Calculate new pitch and yaw positions
+ m_flYaw += m_flYawSpeed;
+ m_flPitch += m_flPitchSpeed;
+
+ // Keep yaw in 0/360 range
+ if (m_flYaw < 0 ) m_flYaw +=360;
+ if (m_flYaw > 360) m_flYaw -=360;
+
+ // ---------------------------------------------
+ // Check yaw and pitch boundaries unless I have
+ // a burn target, or an enemy
+ // ---------------------------------------------
+ if (( HaveInspectTarget() && GetEnemy() == NULL ) ||
+ (!HaveInspectTarget() && !m_pScriptedTarget ) )
+ {
+ bool bInRange = true;
+ if (UTIL_AngleDistance( m_flYaw, m_flYawCenter ) > m_flYawRange)
+ {
+ if (m_fSpotlightFlags & BITS_SPOTLIGHT_SMOOTH_RETURN)
+ {
+ bInRange = false;
+ }
+ else
+ {
+ m_flYaw = m_flYawCenter + m_flYawRange;
+ }
+ m_flYawDir = SPOTLIGHT_SWING_BACK;
+ }
+ else if (UTIL_AngleDistance( m_flYaw, m_flYawCenter ) < -m_flYawRange)
+ {
+ if (m_fSpotlightFlags & BITS_SPOTLIGHT_SMOOTH_RETURN)
+ {
+ bInRange = false;
+ }
+ else
+ {
+ m_flYaw = m_flYawCenter - m_flYawRange;
+ }
+ m_flYawDir = SPOTLIGHT_SWING_FORWARD;
+ }
+ if (UTIL_AngleDistance( m_flPitch, m_flPitchCenter ) > m_flPitchMax)
+ {
+ if (m_fSpotlightFlags & BITS_SPOTLIGHT_SMOOTH_RETURN)
+ {
+ bInRange = false;
+ }
+ else
+ {
+ m_flPitch = m_flPitchCenter + m_flPitchMax;
+ }
+ m_flPitchDir = SPOTLIGHT_SWING_BACK;
+ }
+ else if (UTIL_AngleDistance( m_flPitch, m_flPitchCenter ) < m_flPitchMin)
+ {
+ if (m_fSpotlightFlags & BITS_SPOTLIGHT_SMOOTH_RETURN)
+ {
+ bInRange = false;
+ }
+ else
+ {
+ m_flPitch = m_flPitchCenter + m_flPitchMin;
+ }
+ m_flPitchDir = SPOTLIGHT_SWING_FORWARD;
+ }
+
+ // If in range I'm done doing a smooth return
+ if (bInRange)
+ {
+ m_fSpotlightFlags &= ~BITS_SPOTLIGHT_SMOOTH_RETURN;
+ }
+ }
+ // Convert pitch and yaw to forward angle
+ QAngle vAngle = vec3_angle;
+ vAngle[YAW] = m_flYaw;
+ vAngle[PITCH] = m_flPitch;
+ AngleVectors( vAngle, &m_vSpotlightDir );
+
+ // ---------------------------------------------
+ // Get beam end point. Only collide with
+ // solid objects, not npcs
+ // ---------------------------------------------
+ trace_t tr;
+ AI_TraceLine ( GetAbsOrigin(), GetAbsOrigin() + (m_vSpotlightDir * 2 * m_flSpotlightMaxLength),
+ (CONTENTS_SOLID|CONTENTS_MOVEABLE|CONTENTS_MONSTER|CONTENTS_DEBRIS),
+ this, COLLISION_GROUP_NONE, &tr);
+
+ return (tr.endpos);
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose : Update the direction and position of my spotlight
+// Input :
+// Output :
+//------------------------------------------------------------------------------
+void CNPC_Spotlight::SpotlightUpdate(void)
+{
+ // ---------------------------------------------------
+ // Go back to idle state after a while
+ // ---------------------------------------------------
+ if (m_NPCState == NPC_STATE_ALERT &&
+ m_flLastStateChangeTime + 30 < gpGlobals->curtime )
+ {
+ SetState(NPC_STATE_IDLE);
+ }
+
+ // ---------------------------------------------------
+ // If I don't have a spotlight attempt to create one
+ // ---------------------------------------------------
+ if (!m_pSpotlight &&
+ m_fSpotlightFlags & BITS_SPOTLIGHT_LIGHT_ON )
+ {
+ SpotlightCreate();
+ }
+ if (!m_pSpotlight)
+ {
+ return;
+ }
+
+ // -----------------------------------------------------
+ // If spotlight flag is off destroy spotlight and exit
+ // -----------------------------------------------------
+ if (!(m_fSpotlightFlags & BITS_SPOTLIGHT_LIGHT_ON))
+ {
+ if (m_pSpotlight)
+ {
+ SpotlightDestroy();
+ return;
+ }
+ }
+
+ if (m_fSpotlightFlags & BITS_SPOTLIGHT_TRACK_ON)
+ {
+ // -------------------------------------------
+ // Calculate the new spotlight position
+ // --------------------------------------------
+ m_vSpotlightCurrentPos = SpotlightCurrentPos();
+ }
+ // --------------------------------------------------------------
+ // Update spotlight target velocity
+ // --------------------------------------------------------------
+ Vector vTargetDir = (m_vSpotlightCurrentPos - m_pSpotlightTarget->GetAbsOrigin());
+ float vTargetDist = vTargetDir.Length();
+
+ Vector vecNewVelocity = vTargetDir;
+ VectorNormalize(vecNewVelocity);
+ vecNewVelocity *= (10 * vTargetDist);
+
+ // If a large move is requested, just jump to final spot as we
+ // probably hit a discontinuity
+ if (vecNewVelocity.Length() > 200)
+ {
+ VectorNormalize(vecNewVelocity);
+ vecNewVelocity *= 200;
+ VectorNormalize(vTargetDir);
+ m_pSpotlightTarget->SetLocalOrigin( m_vSpotlightCurrentPos );
+ }
+ m_pSpotlightTarget->SetAbsVelocity( vecNewVelocity );
+ m_pSpotlightTarget->m_vSpotlightOrg = GetAbsOrigin();
+
+ // Avoid sudden change in where beam fades out when cross disconinuities
+ m_pSpotlightTarget->m_vSpotlightDir = m_pSpotlightTarget->GetLocalOrigin() - m_pSpotlightTarget->m_vSpotlightOrg;
+ float flBeamLength = VectorNormalize( m_pSpotlightTarget->m_vSpotlightDir );
+ m_flSpotlightCurLength = (0.60*m_flSpotlightCurLength) + (0.4*flBeamLength);
+
+ // Fade out spotlight end if past max length.
+ if (m_flSpotlightCurLength > 2*m_flSpotlightMaxLength)
+ {
+ m_pSpotlightTarget->SetRenderColorA( 0 );
+ m_pSpotlight->SetFadeLength(m_flSpotlightMaxLength);
+ }
+ else if (m_flSpotlightCurLength > m_flSpotlightMaxLength)
+ {
+ m_pSpotlightTarget->SetRenderColorA( (1-((m_flSpotlightCurLength-m_flSpotlightMaxLength)/m_flSpotlightMaxLength)) );
+ m_pSpotlight->SetFadeLength(m_flSpotlightMaxLength);
+ }
+ else
+ {
+ m_pSpotlightTarget->SetRenderColorA( 1.0 );
+ m_pSpotlight->SetFadeLength(m_flSpotlightCurLength);
+ }
+
+
+ // Adjust end width to keep beam width constant
+ float flNewWidth = m_flSpotlightGoalWidth*(flBeamLength/m_flSpotlightMaxLength);
+ m_pSpotlight->SetEndWidth(flNewWidth);
+
+ // Adjust width of light on the end.
+ if ( FBitSet (m_spawnflags, SF_SPOTLIGHT_NO_DYNAMIC_LIGHT) )
+ {
+ m_pSpotlightTarget->m_flLightScale = 0.0;
+ }
+ else
+ {
+ // <<TODO>> - magic number 1.8 depends on sprite size
+ m_pSpotlightTarget->m_flLightScale = 1.8*flNewWidth;
+ }
+
+ m_pOutputPosition.Set(m_pSpotlightTarget->GetLocalOrigin(),this,this);
+
+#ifdef SPOTLIGHT_DEBUG
+ NDebugOverlay::Cross3D(m_vSpotlightCurrentPos,Vector(-5,-5,-5),Vector(5,5,5),0,255,0,true,0.1);
+ NDebugOverlay::Cross3D(m_vSpotlightTargetPos,Vector(-5,-5,-5),Vector(5,5,5),255,0,0,true,0.1);
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CNPC_Spotlight::Spawn(void)
+{
+ // Check for user error
+ if (m_flSpotlightMaxLength <= 0)
+ {
+ DevMsg("CNPC_Spotlight::Spawn: Invalid spotlight length <= 0, setting to 500\n");
+ m_flSpotlightMaxLength = 500;
+ }
+
+ if (m_flSpotlightGoalWidth <= 0)
+ {
+ DevMsg("CNPC_Spotlight::Spawn: Invalid spotlight width <= 0, setting to 10\n");
+ m_flSpotlightGoalWidth = 10;
+ }
+
+ if (m_flSpotlightGoalWidth > MAX_BEAM_WIDTH)
+ {
+ DevMsg("CNPC_Spotlight::Spawn: Invalid spotlight width %.1f (max %.1f)\n", m_flSpotlightGoalWidth, MAX_BEAM_WIDTH );
+ m_flSpotlightGoalWidth = MAX_BEAM_WIDTH;
+ }
+
+ Precache();
+
+ // This is a dummy model that is never used!
+ SetModel( "models/player.mdl" );
+
+ // No Hull for now
+ UTIL_SetSize(this,vec3_origin,vec3_origin);
+
+ SetSolid( SOLID_BBOX );
+ AddSolidFlags( FSOLID_NOT_STANDABLE );
+ m_bloodColor = DONT_BLEED;
+ SetViewOffset( Vector(0, 0, 10) ); // Position of the eyes relative to NPC's origin.
+ m_flFieldOfView = VIEW_FIELD_FULL;
+ m_NPCState = NPC_STATE_IDLE;
+
+ CapabilitiesAdd( bits_CAP_SQUAD);
+
+ // ------------------------------------
+ // Init all class vars
+ // ------------------------------------
+ m_vInspectPos = vec3_origin;
+ m_flInspectEndTime = 0;
+ m_flNextEntitySearchTime= gpGlobals->curtime + SPOTLIGHT_ENTITY_INSPECT_DELAY;
+ m_flNextHintSearchTime = gpGlobals->curtime + SPOTLIGHT_HINT_INSPECT_DELAY;
+ m_bHadEnemy = false;
+
+ m_vSpotlightTargetPos = vec3_origin;
+ m_vSpotlightCurrentPos = vec3_origin;
+ m_pSpotlight = NULL;
+ m_pSpotlightTarget = NULL;
+ m_vSpotlightDir = vec3_origin;
+ //m_nHaloSprite // Set in precache
+ m_flSpotlightCurLength = m_flSpotlightMaxLength;
+
+ m_flYaw = 0;
+ m_flYawSpeed = 0;
+ m_flYawCenter = GetLocalAngles().y;
+ m_flYawDir = random->RandomInt(0,1) ? 1 : -1;
+ //m_flYawRange = 90; // Keyfield in WC
+
+ m_flPitch = 0;
+ m_flPitchSpeed = 0;
+ m_flPitchCenter = GetLocalAngles().x;
+ m_flPitchDir = random->RandomInt(0,1) ? 1 : -1;
+ //m_flPitchMin = 35; // Keyfield in WC
+ //m_flPitchMax = 50; // Keyfield in WC
+ //m_flIdleSpeed = 2; // Keyfield in WC
+ //m_flAlertSpeed = 5; // Keyfield in WC
+
+ m_fSpotlightFlags = 0;
+ if (FBitSet ( m_spawnflags, SF_SPOTLIGHT_START_TRACK_ON ))
+ {
+ m_fSpotlightFlags |= BITS_SPOTLIGHT_TRACK_ON;
+ }
+ if (FBitSet ( m_spawnflags, SF_SPOTLIGHT_START_LIGHT_ON ))
+ {
+ m_fSpotlightFlags |= BITS_SPOTLIGHT_LIGHT_ON;
+ }
+
+ // If I'm never moving just turn on the spotlight and don't think again
+ if (FBitSet ( m_spawnflags, SF_SPOTLIGHT_NEVER_MOVE ))
+ {
+ SpotlightCreate();
+ }
+ else
+ {
+ NPCInit();
+ SetThink(CallNPCThink);
+ }
+
+ AddEffects( EF_NODRAW );
+ SetMoveType( MOVETYPE_NONE );
+ SetGravity( 0.0 );
+}
+
+//------------------------------------------------------------------------------
+// Purpose: Inputs
+//------------------------------------------------------------------------------
+void CNPC_Spotlight::InputLightOn( inputdata_t &inputdata )
+{
+ m_fSpotlightFlags |= BITS_SPOTLIGHT_LIGHT_ON;
+}
+
+void CNPC_Spotlight::InputLightOff( inputdata_t &inputdata )
+{
+ m_fSpotlightFlags &= ~BITS_SPOTLIGHT_LIGHT_ON;
+}
+
+void CNPC_Spotlight::InputTrackOn( inputdata_t &inputdata )
+{
+ m_fSpotlightFlags |= BITS_SPOTLIGHT_TRACK_ON;
+}
+
+void CNPC_Spotlight::InputTrackOff( inputdata_t &inputdata )
+{
+ m_fSpotlightFlags &= ~BITS_SPOTLIGHT_TRACK_ON;
+}
+
+
+//------------------------------------------------------------------------------
+// Purpose : Starts cremator doing scripted burn to a location
+//------------------------------------------------------------------------------
+void CNPC_Spotlight::SetScriptedTarget( CScriptedTarget *pScriptedTarget )
+{
+ if (pScriptedTarget)
+ {
+ m_pScriptedTarget = pScriptedTarget;
+ m_vSpotlightTargetPos = m_pScriptedTarget->GetAbsOrigin();
+ }
+ else
+ {
+ m_pScriptedTarget = NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: This is a generic function (to be implemented by sub-classes) to
+// handle specific interactions between different types of characters
+// (For example the barnacle grabbing an NPC)
+// Input : Constant for the type of interaction
+// Output : true - if sub-class has a response for the interaction
+// false - if sub-class has no response
+//-----------------------------------------------------------------------------
+bool CNPC_Spotlight::HandleInteraction(int interactionType, void *data, CBaseCombatCharacter* sourceEnt)
+{
+ if (interactionType == g_interactionScriptedTarget)
+ {
+ // If I already have a scripted target, reject the new one
+ if (m_pScriptedTarget && sourceEnt)
+ {
+ return false;
+ }
+ else
+ {
+ SetScriptedTarget((CScriptedTarget*)sourceEnt);
+ return true;
+ }
+ }
+ return false;
+}