Modified source engine (2017) developed by valve and leaked in 2020. Not for commercial purporses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1545 lines
45 KiB

5 years ago
//========= 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;
}