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