diff options
Diffstat (limited to 'game/server/hl2/npc_spotlight.cpp')
| -rw-r--r-- | game/server/hl2/npc_spotlight.cpp | 1544 |
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; +} |