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